diff --git a/.dockerignore b/.dockerignore index 2aaa4394b..5fca33cea 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,4 +5,5 @@ vagrant/ nix/ flake.nix flake.lock - +var/ +srv/ diff --git a/.gitattributes b/.gitattributes index 07764a78d..9bf4dc164 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,35 @@ -* text eol=lf \ No newline at end of file +* text eol=lf + +# Images +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.bmp binary +*.ico binary +*.webp binary +*.tiff binary +*.tif binary +*.svg binary +*.avif binary +*.heic binary +*.heif binary + +# Audio +*.mp3 binary +*.wav binary +*.ogg binary +*.flac binary +*.aac binary +*.m4a binary +*.wma binary + +# Video +*.mp4 binary +*.mov binary +*.avi binary +*.mkv binary +*.webm binary +*.flv binary +*.wmv binary +*.m4v binary \ No newline at end of file diff --git a/.github/docker/default_ssl.conf b/.github/docker/default_ssl.conf index 9ec5c10db..c5dd44255 100644 --- a/.github/docker/default_ssl.conf +++ b/.github/docker/default_ssl.conf @@ -8,7 +8,8 @@ server { } server { - listen 443 ssl http2; + listen 443 ssl; + http2 on; server_name ; root /app/public; @@ -61,7 +62,6 @@ server { fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; - include /etc/nginx/fastcgi_params; } location ~ /\.ht { diff --git a/.github/docker/entrypoint-post.sh b/.github/docker/entrypoint-post.sh deleted file mode 100644 index f2ede4492..000000000 --- a/.github/docker/entrypoint-post.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/ash -e -cd /app - -# Directory and log setup -mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php7/ \ - && chmod 777 /var/log/panel/logs/ \ - && ln -s /app/storage/logs/ /var/log/panel/ - -# Check mounted /app/var directory -if [ ! -d /app/var ]; then - echo "You must mount the /app/var directory to the container." - exit 1 -fi - -# .env file handling -if [ ! -f /app/var/.env ]; then - echo "Creating .env file." - touch /app/var/.env -fi - -rm -f /app/.env -ln -s /app/var/.env /app/ - -# Environment configuration -( - source /app/.env - - if [ -z "$APP_KEY" ]; then - echo "Generating APP_KEY" - echo "APP_KEY=" >> /app/.env - APP_ENVIRONMENT_ONLY=true php artisan key:generate - fi - - if [ -z "$HASHIDS_LENGTH" ]; then - echo "Defaulting HASHIDS_LENGTH to 8" - echo "HASHIDS_LENGTH=8" >> /app/.env - fi - - if [ -z "$HASHIDS_SALT" ]; then - echo "Generating HASHIDS_SALT" - HASHIDS_SALT=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1) - echo "HASHIDS_SALT=$HASHIDS_SALT" >> /app/.env - fi -) - -# SSL configuration -echo "Checking if https is required." -if [ -f /etc/nginx/http.d/panel.conf ]; then - echo "Using nginx config already in place." - if [ $LE_EMAIL ]; then - echo "Checking for cert update" - certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n - fi -else - if [ -z $LE_EMAIL ]; then - echo "Using http config." - cp .github/docker/default.conf /etc/nginx/http.d/panel.conf - else - echo "Configuring SSL" - cp .github/docker/default_ssl.conf /etc/nginx/http.d/panel.conf - sed -i "s||$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/http.d/panel.conf - certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n - fi - rm -rf /etc/nginx/http.d/default.conf -fi - -# Database configuration -if [[ -z $DB_PORT ]]; then - DB_PORT=3306 - echo "DB_PORT not specified, defaulting to 3306" -fi - -# Wait for database -echo "Checking database status." -until nc -z -v -w30 $DB_HOST $DB_PORT -do - echo "Waiting for database connection..." - sleep 1 -done - -# Database migration with seeding protection -echo "Checking database migrations." -if ! php artisan migrate:status | grep -q "No migrations found"; then - # Only run migrations if needed - php artisan migrate --force - - # Check if we need to seed (only if migrations ran) - if php artisan migrate:status | grep -q "Ran"; then - echo "Running database seed if needed." - php artisan db:seed --force - fi -fi - -# Start services -echo "Starting cron jobs." -crond -L /var/log/crond -l 5 - -echo "Starting supervisord." -exec "$@" diff --git a/.github/docker/entrypoint.sh b/.github/docker/entrypoint.sh index 4ea80d5e9..92ee0f7ad 100644 --- a/.github/docker/entrypoint.sh +++ b/.github/docker/entrypoint.sh @@ -5,6 +5,10 @@ mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php && chmod 777 /var/log/panel/logs/ \ && ln -s /app/storage/logs/ /var/log/panel/ +# Ensure proper permissions for Laravel storage directories +mkdir -p /app/storage/logs /app/storage/framework/cache /app/storage/framework/sessions /app/storage/framework/views \ + && chmod -R 777 /app/storage/ + # Check that user has mounted the /app/var directory if [ ! -d /app/var ]; then echo "You must mount the /app/var directory to the container." @@ -91,12 +95,90 @@ do done ## make sure the db is set up -echo -e "Migrating and Seeding D.B" -php artisan migrate --seed --force +echo -e "Migrating Database" +php artisan migrate --force + +if [ "$SKIP_SEED" != "True" ]; then + echo -e "Seeding database" + php artisan migrate --seed --force +else + echo -e "Skipping database seeding (SKIP_SEED=True)" +fi + +# Setup development environment if specified +( + source /app/.env + if [ "$PYRODACTYL_DOCKER_DEV" = "true" ] && [ "$DEV_SETUP" != "true" ]; then + echo -e "\e[42mDevelopment environment detected, setting up development resources...\e[0m" + + # Create a developer user + php artisan p:user:make -n --email dev@pyro.host --username dev --name-first Developer --name-last User --password password + mariadb -u root -h database -p"$DB_ROOT_PASSWORD" --ssl=0 -e "USE panel; UPDATE users SET root_admin = 1;" # workaround because --admin is broken + + # Make a location and node for the panel + php artisan p:location:make -n --short local --long Local + php artisan p:node:make -n --name local --description "Development Node" --locationId 1 --fqdn localhost --internal-fqdn $WINGS_INTERNAL_IP --public 1 --scheme http --proxy 0 --maxMemory 1024 --maxDisk 10240 --overallocateMemory 0 --overallocateDisk 0 + + echo "Adding dummy allocations..." + mariadb -u root -h database -p"$DB_ROOT_PASSWORD" --ssl=0 -e "USE panel; INSERT INTO allocations (node_id, ip, port) VALUES (1, '0.0.0.0', 25565), (1, '0.0.0.0', 25566), (1, '0.0.0.0', 25567);" + + echo "Creating database user..." + mariadb -u root -h database -p"$DB_ROOT_PASSWORD" --ssl=0 -e "CREATE USER 'pterodactyluser'@'%' IDENTIFIED BY 'somepassword'; GRANT ALL PRIVILEGES ON *.* TO 'pterodactyluser'@'%' WITH GRANT OPTION;" + + # Configure node + export WINGS_CONFIG=/etc/pterodactyl/config.yml + mkdir -p $(dirname $WINGS_CONFIG) + echo "Fetching and modifying Wings configuration file..." + CONFIG=$(php artisan p:node:configuration 1) + + # Allow all origins for CORS + CONFIG=$(printf "%s\nallowed_origins: ['*']" "$CONFIG") + + # Update Wings configuration paths if WINGS_DIR is set + if [ -z "$WINGS_DIR" ]; then + echo "WINGS_DIR is not set, using default paths." + else + echo "Updating Wings configuration paths to '$WINGS_DIR'..." + + # add system section if it doesn't exist + if ! echo "$CONFIG" | grep -q "^system:"; then + CONFIG=$(printf "%s\nsystem:" "$CONFIG") + fi + + update_config() { + local key="$1" + local value="$2" + + # update existing key or add new one + if echo "$CONFIG" | grep -q "^ $key:"; then + CONFIG=$(echo "$CONFIG" | sed "s|^ $key:.*| $key: $value|") + else + CONFIG=$(echo "$CONFIG" | sed "/^system:/a\\ $key: $value") + fi + } + + update_config "root_directory" "$WINGS_DIR/srv/wings/" + update_config "log_directory" "$WINGS_DIR/srv/wings/logs/" + update_config "data" "$WINGS_DIR/srv/wings/volumes" + update_config "archive_directory" "$WINGS_DIR/srv/wings/archives" + update_config "backup_directory" "$WINGS_DIR/srv/wings/backups" + update_config "tmp_directory" "$WINGS_DIR/srv/wings/tmp/" + fi + + echo "Saving Wings configuration file to '$WINGS_CONFIG'..." + echo "$CONFIG" > $WINGS_CONFIG + + # Mark setup as complete + echo "DEV_SETUP=true" >> /app/.env + echo "Development setup complete." + elif [ "$DEV_SETUP" = "true" ]; then + echo "Skipping development setup, already completed." + fi +) ## start cronjobs for the queue echo -e "Starting cron jobs." crond -L /var/log/crond -l 5 echo -e "Starting supervisord." -exec "$@" \ No newline at end of file +exec "$@" diff --git a/.github/workflows/release.yaml b/.github/workflows-disabled/release.yaml similarity index 100% rename from .github/workflows/release.yaml rename to .github/workflows-disabled/release.yaml diff --git a/.github/workflows/build-and-release.yaml b/.github/workflows/build-and-release.yaml new file mode 100644 index 000000000..68a935a8a --- /dev/null +++ b/.github/workflows/build-and-release.yaml @@ -0,0 +1,101 @@ +name: Docker Build and Release Workflow + +on: + push: + branches: + - main + release: + types: + - published + +permissions: + packages: write + contents: write + +jobs: + build-dev: + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, 'Update version to') + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push canary image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:canary + ghcr.io/${{ github.repository }}:dev + + platforms: linux/amd64 + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 + + + build-release: + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push release image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:main + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 + + update-version: + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - name: Checkout main branch at release commit + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.target_commitish }} + + - name: Update version in config/app.php + run: sed -i "s/'version' => 'canary'/'version' => '${{ github.event.release.tag_name }}'/g" config/app.php + + - name: Commit and push to release branch + run: | + git config user.name "pyrodactyl-ci" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -B release + git add config/app.php + git commit -m "Update version to ${{ github.event.release.tag_name }} in config/app.php" + git push origin release --force + diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml deleted file mode 100644 index 658425969..000000000 --- a/.github/workflows/docker.yaml +++ /dev/null @@ -1,72 +0,0 @@ -name: Docker - -on: - workflow_run: - workflows: ['Release'] - types: - - completed - push: - branches: - - release/** - - main - release: - types: - - published - -permissions: - packages: write - -jobs: - push: - name: Push - runs-on: ubuntu-latest - steps: - - name: Code checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.workflow_run.head_branch || github.ref }} - - - name: Docker metadata - id: docker_meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/pyrohost/pyrodactyl - flavor: | - latest=auto - tags: | - type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }} - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' && !github.event.release.prerelease }} - type=ref,event=tag - type=raw,value=${{ github.event.workflow_run.head_branch || github.ref_name }},enable=${{ startsWith(github.ref, 'refs/heads/release/') }} - - - name: Setup QEMU - uses: docker/setup-qemu-action@v3 - - - name: Setup Docker buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Update version - if: "github.event_name == 'release' && github.event.action == 'published'" - env: - REF: ${{ github.event.release.tag_name }} - run: | - sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:1}',/" config/app.php - - - name: Build and Push - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - push: true - platforms: linux/amd64,linux/arm64 - labels: ${{ steps.docker_meta.outputs.labels }} - tags: ${{ steps.docker_meta.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 6d8f5bfcb..7ef3cbd69 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,10 @@ _ide_helper_models.php public/assets/manifest.json # For local development with docker -#docker-compose.yml +docker-compose.yml +local_docker +/srv +/var # for image related files misc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 504263830..a1c212e30 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,3 @@ -# Contributing - -pyro.host does not accept Pull Requests (PRs) _for new functionality_ from users that are not currently employed -or affiliated by Pyro. It has become overwhelming to try and give the proper time and attention that such -complicated PRs tend to require — and deserve. As a result, it is in the project's best interest to limit the scope -of work on new functionality to work done within the Pyro team. - -PRs that address existing _bugs_ with a corresponding issue opened in our issue tracker will continue to be accepted -and reviewed. Their scope is often significantly more targeted, and simply improving upon existing and well defined -logic. - ### Responsible Disclosure This is a fairly in-depth project and makes use of a lot of parts. We strive to keep everything as secure as possible @@ -20,7 +9,7 @@ publicly disclose whatever issue you have found. We understand how frustrating i no one will respond to you. This holds us to a standard of providing prompt attention to any issues that arise and keeping this community safe. -If you've found what you believe is a security issue please email `team@pyro.host`. Please check +If you've found what you believe is a security issue please email `naterfute@pyro.host`. Please check [SECURITY.md](/SECURITY.md) for additional details. ### Contact Us diff --git a/Dockerfile b/Dockerfile index 50f569fc7..a9e8c67c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,60 +1,98 @@ +# TODO: Refactor Docker with stricter permissions & modernized tooling + # Stage 0: -# Build the frontend +# Build the frontend (only if not in dev mode) FROM --platform=$TARGETOS/$TARGETARCH node:lts-alpine AS frontend +ARG DEV=false WORKDIR /app -COPY . ./ -RUN apk add --no-cache --update git \ - && npm install -g turbo \ - && npm ci \ - && npm run ship \ - && apk del git +RUN if [ "$DEV" = "false" ]; then \ + apk add --no-cache git \ + && npm install -g corepack@latest turbo \ + && corepack enable \ + && echo "Building frontend"; \ + fi +COPY pnpm-lock.yaml package.json ./ +RUN if [ "$DEV" = "false" ]; then \ + pnpm fetch \ + && echo "Fetched dependencies"; \ + fi +COPY . . +RUN if [ "$DEV" = "false" ]; then \ + pnpm install --frozen-lockfile \ + && pnpm run ship; \ + fi # Stage 1: -# Build the actual container with all of the needed PHP dependencies that will run the application. -FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine +# Build the actual container with all of the needed PHP dependencies that will run the application +FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine AS php +ARG DEV=false WORKDIR /app -COPY . ./ -COPY --from=frontend /app/public/assets ./public/assets -COPY --from=frontend /app/public/build ./public/build -RUN apk add --no-cache --update \ - ca-certificates \ - dcron \ - curl \ - git \ - supervisor \ - tar \ - unzip \ - nginx \ - libpng-dev \ - libxml2-dev \ - libzip-dev \ - postgresql-dev \ - certbot \ - certbot-nginx \ - mysql-client \ +# Build-time deps & PHP extensions +RUN apk add --no-cache --virtual .build-deps \ + libpng-dev libxml2-dev libzip-dev postgresql-dev \ && docker-php-ext-configure zip \ && docker-php-ext-install bcmath gd pdo pdo_mysql pdo_pgsql zip \ - && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ - && cp .env.example .env \ - && mkdir -p bootstrap/cache/ storage/logs storage/framework/sessions storage/framework/views storage/framework/cache \ - && chmod -R 777 bootstrap storage \ - && composer install --no-dev --optimize-autoloader \ - && rm -rf .env bootstrap/cache/*.php \ - && mkdir -p /app/storage/logs/ \ - && chown -R nginx:nginx . + && apk del .build-deps \ + && apk add --no-cache \ + libpng libxml2 libzip libpq +# Runtime packages +RUN apk add --no-cache \ + ca-certificates curl git supervisor nginx dcron \ + tar unzip certbot certbot-nginx mysql-client \ + && ln -s /bin/ash /bin/bash + +# Copy frontend build +COPY . ./ +RUN if [ "$DEV" = "false" ]; then \ + echo "Copying frontend build"; \ + else \ + mkdir -p public/assets public/build; \ + fi +COPY --from=frontend /app/public/assets public/assets +COPY --from=frontend /app/public/build public/build + +# Fetch & install Composer packages +COPY composer.json composer.lock ./ +RUN curl -sS https://getcomposer.org/installer \ + | php -- --install-dir=/usr/local/bin --filename=composer \ + && composer install --no-dev --optimize-autoloader + +# Clean up image for dev environment +# This is because we share local files with the container +RUN if [ "$DEV" = "true" ]; then \ + echo "Cleaning up"; \ + find . \ + -mindepth 1 \ + \( -path './vendor*' \) -prune \ + -o \ + -exec rm -rf -- {} \; \ + >/dev/null 2>&1; \ + fi; \ + exit 0 + +# Env, directories, permissions +RUN mkdir -p bootstrap/cache storage/logs storage/framework/sessions storage/framework/views storage/framework/cache; \ + rm -rf bootstrap/cache/*.php; \ + chown -R nginx:nginx .; \ + chmod -R 777 bootstrap storage; \ + cp .env.example .env || true; + +# Cron jobs & NGINX tweaks RUN rm /usr/local/etc/php-fpm.conf \ - && echo "* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1" >> /var/spool/cron/crontabs/root \ - && echo "0 23 * * * certbot renew --nginx --quiet" >> /var/spool/cron/crontabs/root \ - && sed -i s/ssl_session_cache/#ssl_session_cache/g /etc/nginx/nginx.conf \ + && { \ + echo "* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1"; \ + echo "0 23 * * * certbot renew --nginx --quiet"; \ + } > /var/spool/cron/crontabs/root \ + && sed -i 's/ssl_session_cache/#ssl_session_cache/' /etc/nginx/nginx.conf \ && mkdir -p /var/run/php /var/run/nginx +# Configs COPY --chown=nginx:nginx .github/docker/default.conf /etc/nginx/http.d/default.conf -COPY --chown=nginx:nginx .github/docker/www.conf /usr/local/etc/php-fpm.conf -COPY --chown=nginx:nginx .github/docker/supervisord.conf /etc/supervisord.conf +COPY --chown=nginx:nginx .github/docker/www.conf /usr/local/etc/php-fpm.conf +COPY --chown=nginx:nginx .github/docker/supervisord.conf /etc/supervisord.conf -RUN ln -s /bin/ash /bin/bash EXPOSE 80 443 ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ] diff --git a/README.md b/README.md index c574b033c..08b2e9a2f 100755 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ > [!WARNING] > Pyrodactyl is under development and pre-release. Some UI elements may appear broken, and there might be some bugs. +> [!NOTE] +> Please read our documentation at [https://pyrodactyl.dev](https://pyrodactyl.dev) before installing. + > [!IMPORTANT] > For Pyrodactyl-specific issues, please use [Pyrodactyl GitHub Discussions](https://github.com/pyrohost/pyrodactyl/discussions) or the [Pyrodactyl Discord](https://discord.gg/UhuYKKK2uM?utm_source=githubreadme&utm_medium=readme&utm_campaign=OSSLAUNCH&utm_id=OSSLAUNCH) instead of Pterodactyl or Pelican support channels. @@ -30,30 +33,25 @@ Pyrodactyl is the Pterodactyl-based game server management panel that focuses on ## Changes from vanilla Pterodactyl -- **Smaller bundle sizes:** Pyrodactyl is built using Vite, and significant re-architecting of the application means Pyrodactyl's initial download size is over **[170 times smaller than leading Pterodactyl forks, and Pelican](https://i.imgur.com/tKWLHhR.png)** -- **Faster build times:** Pyrodactyl completes builds in milliseconds with the power of Turbo. Cold builds with zero cache finish in **under 7 seconds**. -- **Faster loading times:** Pyrodactyl's load times are, on average, **[over 16 times faster](https://i.imgur.com/28XxmMi.png)** than other closed-source Pterodactyl forks and Pelican. Smarter code splitting and chunking means that pages you visit in the panel only load necessary resources on demand. Better caching means that everything is simply _snappy_. -- **More secure:** Pyrodactyl's modern architecture means **most severe and easily exploitable CVEs simply do not exist**. We have also implemented SRI and integrity checks for production builds. -- **More accessible:** Pyro believes that gaming should be easily available for everyone. Pyrodactyl builds with the latest Web accessibility guidelines in mind. Pyrodactyl is **entirely keyboard-navigable, even context menus.**, and screen-readers are easily compatible. -- **More approachable:** Pyrodactyl's friendly, approachable interface means that anyone can confidently run a game server. +- **Smaller bundle sizes:** Pyrodactyl is built using Vite, and significant design changes mean Pyrodactyl's initial download size is over **[170 times smaller than leading Pterodactyl forks, including Pelican](https://i.imgur.com/tKWLHhR.png)**. +- **Faster build times:** Pyrodactyl completes builds in milliseconds with the power of Turbo. Cold builds with zero cache finish in **under 7 seconds**. +- **Faster loading times:** Pyrodactyl's load times are, on average, **[over 16 times faster](https://i.imgur.com/28XxmMi.png)** than other closed-source Pterodactyl forks and Pelican. Smarter code splitting and chunking means that pages you visit in the panel only load necessary resources on demand. Better caching means that everything is simply _snappy_. +- **More secure:** Pyrodactyl's modern architecture means **most severe and easily exploitable CVEs simply do not exist**. We have also implemented SRI and integrity checks for production builds. +- **More accessible:** Pyro believes that gaming should be easily available for everyone. Pyrodactyl builds with the latest Web accessibility guidelines in mind. Pyrodactyl is **entirely keyboard-navigable, even context menus**, and screen-readers are easily compatible. +- **More approachable:** Pyrodactyl's friendly, approachable interface means that anyone can confidently run a game server. -[![Dashboard Image](https://i.imgur.com/kHHOW6P.jpeg)] +![Dashboard Image](https://i.imgur.com/kHHOW6P.jpeg) ## Installing Pyrodactyl -See our [Installation](https://pyrodactyl.dev/docs/installation) wiki page on how to get started. +See our [Installation](https://pyrodactyl.dev/docs/installation) docs page on how to get started. > [!NOTE] > Windows is currently only supported for development purposes. ## Local Development -See our development pages on how to get started: - -- [Local Dev on Windows (Vagrant)]() -- [Local Dev on Linux (Vagrant)]() -- [Local Dev on Linux (Nix)]() - - Nix is recommended if you can't get Vagrant working +Pyrodactyl has various effortless ways of starting up a ready-to-use, fully-featured development environment. See our [Local Development](https://pyrodactyl.dev/docs/local-development) documentation for more information. ## Star History diff --git a/Vagrantfile b/Vagrantfile index 1578ce09f..2afecba07 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,52 +1,98 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +BOX_DEFAULT = "ubuntu/jammy64" +BOX_LIBVIRT = "generic/ubuntu2204" + +RAM = (ENV["VM_RAM"] || "8192").to_i +CPUS = (ENV["VM_CPUS"] || "8").to_i + +SPECIAL_PORTS = [ + 3000, # pyrodactyl web ui + 3306, # database + 8080, # phpmyadmin + 8025, # mailpit web ui + 9000, # minio api + 9001 # minio console +] + +TEST_PORTS = (25500..25600) + +FORWARDED_PORTS = SPECIAL_PORTS + TEST_PORTS.to_a + Vagrant.configure("2") do |config| - config.vm.box = "almalinux/9" - config.vm.network "forwarded_port", guest: 3000, host: 3000, host_ip: "localhost" - config.vm.network "forwarded_port", guest: 8080, host: 8080, host_ip: "localhost" + # Base box and hostname + config.vm.box = BOX_DEFAULT + config.vm.hostname = "pyrodactyl-dev" - # you need enough RAM for packages to install properly - config.vm.provider "virtualbox" do |vb| - vb.memory = "4096" - vb.cpus = "4" - vb.cpuexecutioncap = 95 - vb.customize ["modifyvm", :id, "--nictype1", "virtio"] - vb.customize ["modifyvm", :id, "--nictype2", "virtio"] - vb.customize ["storagectl", :id, "--name", "IDE Controller", "--remove"] - vb.customize ["storagectl", :id, "--name", "SATA Controller", "--add", "sata"] - vb.customize ["modifyvm", :id, "--boot1", "disk"] - vb.customize ["modifyvm", :id, "--nic1", "nat"] + # Forwarded ports + FORWARDED_PORTS.each do |p| + config.vm.network "forwarded_port", + guest: p, + host: p, + host_ip: "127.0.0.1", + auto_correct: false + end - end - config.vm.provider "vmware_desktop" do |v| - v.vmx["memsize"] = "4096" - v.vmx["numvcpus"] = "4" - v.vmx["tools.upgrade.policy"] = "manual" - v.vmx["RemoteDisplay.vnc.enabled"] = "FALSE" - v.vmx["vhv.enable"] = "FALSE" - v.vmx["ethernet0.connectionType"] = "nat" - v.vmx["ethernet0.wakeOnPacketTx"] = "TRUE" - v.vmx["ethernet0.addressType"] = "generated" - end - # Libvirt provider - config.vm.provider "libvirt" do |libvirt| - libvirt.memory = 8192 - libvirt.cpus = 4 - config.vm.network "public_network", dev: "bridge0" + # VirtualBox provider settings + config.vm.provider "virtualbox" do |vb| + vb.name = "pyrodactyl-dev" + vb.memory = RAM + vb.cpus = CPUS + vb.gui = false - end - # setup the synced folder and provision the VM - config.vm.synced_folder ".", "/var/www/pterodactyl" - # type: "virtualbox" - # nfs_version: 4 + vb.customize ["modifyvm", :id, "--cpuexecutioncap", "95"] + vb.customize ["modifyvm", :id, "--nic1", "nat"] + vb.customize ["modifyvm", :id, "--nictype1", "virtio"] + end - config.vm.provision "shell", path: "vagrant/provision.sh" + # VMware provider settings + config.vm.provider "vmware_desktop" do |v| + v.vmx["memsize"] = RAM.to_s + v.vmx["numvcpus"] = CPUS.to_s + v.vmx["tools.upgrade.policy"] = "manual" + v.vmx["RemoteDisplay.vnc.enabled"] = "FALSE" + v.vmx["vhv.enable"] = "FALSE" + v.vmx["ethernet0.connectionType"] = "nat" + v.vmx["ethernet0.wakeOnPacketTx"] = "TRUE" + v.vmx["ethernet0.addressType"] = "generated" + end - config.vm.hostname = "pyrodactyl-dev" + # Libvirt provider settings + config.vm.provider "libvirt" do |lv, override| + override.vm.box = BOX_LIBVIRT + lv.memory = RAM + lv.cpus = CPUS + end + # Synced folder configuration + if Vagrant::Util::Platform.windows? + # Use VirtualBox shared folders on Windows (no authentication) + config.vm.synced_folder ".", "/var/www/pterodactyl", + type: "virtualbox", + owner: "vagrant", + group: "vagrant", + mount_options: ["dmode=755", "fmode=644"] + else + # Use NFS on Linux/macOS + config.vm.synced_folder ".", "/var/www/pterodactyl", + type: "nfs", + nfs_version: 4, + nfs_udp: false, + mount_options: ["rw", "vers=4", "tcp", "fsc", "rsize=1048576", "wsize=1048576"] + end - config.vm.post_up_message = "Pterodactyl is up and running at http://localhost:3000. Login with username: dev@pyro.host, password: 'password'." + # Provisioning script + config.vm.provision "shell", + path: "vagrant/provision.sh", + keep_color: true, + privileged: true - # allocated testing ports - config.vm.network "forwarded_port", guest: 25565, host: 25565, host_ip: "localhost" - config.vm.network "forwarded_port", guest: 25566, host: 25566, host_ip: "localhost" - config.vm.network "forwarded_port", guest: 25567, host: 25567, host_ip: "localhost" + # Helpful post-up message + config.vm.post_up_message = <<~MSG + Pyrodactyl is up and running at http://localhost:3000 + Login with: + username: dev@pyro.host + password: password + MSG end diff --git a/app/Console/Commands/Maintenance/DeleteOrphanedBackupsCommand.php b/app/Console/Commands/Maintenance/DeleteOrphanedBackupsCommand.php new file mode 100644 index 000000000..7627ae1cc --- /dev/null +++ b/app/Console/Commands/Maintenance/DeleteOrphanedBackupsCommand.php @@ -0,0 +1,129 @@ +option('dry-run'); + + // Find backups that reference non-existent servers including + // soft-deleted backups since they might be orphaned too + $orphanedBackups = Backup::withTrashed() + ->whereDoesntHave('server') + ->get(); + + if ($orphanedBackups->isEmpty()) { + $this->info('No orphaned backups found.'); + return; + } + + $count = $orphanedBackups->count(); + $totalSize = $orphanedBackups->sum('bytes'); + + if ($isDryRun) { + $this->warn("Found {$count} orphaned backup(s) that would be deleted (Total size: {$this->formatBytes($totalSize)}):"); + + $this->table( + ['ID', 'UUID', 'Name', 'Server ID', 'Disk', 'Size', 'Status', 'Created At'], + $orphanedBackups->map(function (Backup $backup) { + return [ + $backup->id, + $backup->uuid, + $backup->name, + $backup->server_id, + $backup->disk, + $this->formatBytes($backup->bytes), + $backup->trashed() ? 'Soft Deleted' : 'Active', + $backup->created_at->format('Y-m-d H:i:s'), + ]; + })->toArray() + ); + + $this->info('Run without --dry-run to actually delete these backups.'); + return; + } + + if (!$this->confirm("Are you sure you want to delete {$count} orphaned backup(s) ({$this->formatBytes($totalSize)})? This action cannot be undone.")) { + $this->info('Operation cancelled.'); + return; + } + + $this->warn("Deleting {$count} orphaned backup(s) ({$this->formatBytes($totalSize)})..."); + + $deletedCount = 0; + $failedCount = 0; + + foreach ($orphanedBackups as $backup) { + try { + // If backup is already soft-deleted, force delete it completely + if ($backup->trashed()) { + $backup->forceDelete(); + $deletedCount++; + $this->info("Force deleted soft-deleted backup: {$backup->uuid} ({$backup->name}) - {$this->formatBytes($backup->bytes)}"); + } else { + // Use the service to properly delete from storage and database + $this->deleteBackupService->handle($backup); + $deletedCount++; + $this->info("Deleted backup: {$backup->uuid} ({$backup->name}) - {$this->formatBytes($backup->bytes)}"); + } + } catch (\Exception $exception) { + $failedCount++; + $this->error("Failed to delete backup {$backup->uuid}: {$exception->getMessage()}"); + + // If we can't delete from storage, at least remove the database record + try { + if ($backup->trashed()) { + $backup->forceDelete(); + $this->warn("Force deleted soft-deleted backup {$backup->uuid} (storage deletion failed)"); + } else { + $backup->delete(); + $this->warn("Removed database record for backup {$backup->uuid} (storage deletion failed)"); + } + } catch (\Exception $dbException) { + $this->error("Failed to remove database record for backup {$backup->uuid}: {$dbException->getMessage()}"); + } + } + } + + $this->info("Cleanup completed. Deleted: {$deletedCount}, Failed: {$failedCount}"); + } + + /** + * Format bytes into human readable format. + */ + private function formatBytes(int $bytes): string + { + if ($bytes === 0) { + return '0 B'; + } + + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $base = 1024; + $exponent = floor(log($bytes) / log($base)); + $exponent = min($exponent, count($units) - 1); + + $value = $bytes / pow($base, $exponent); + $unit = $units[$exponent]; + + return sprintf('%.2f %s', $value, $unit); + } +} \ No newline at end of file diff --git a/app/Console/Commands/Node/MakeNodeCommand.php b/app/Console/Commands/Node/MakeNodeCommand.php index 2d2623ad7..f3ba12223 100644 --- a/app/Console/Commands/Node/MakeNodeCommand.php +++ b/app/Console/Commands/Node/MakeNodeCommand.php @@ -12,6 +12,7 @@ class MakeNodeCommand extends Command {--description= : A description to identify the node.} {--locationId= : A valid locationId.} {--fqdn= : The domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node.} + {--internal-fqdn= : Internal domain name for panel-to-Wings communication (optional).} {--public= : Should the node be public or private? (public=1 / private=0).} {--scheme= : Which scheme should be used? (Enable SSL=https / Disable SSL=http).} {--proxy= : Is the daemon behind a proxy? (Yes=1 / No=0).} @@ -51,6 +52,7 @@ class MakeNodeCommand extends Command 'https' ); $data['fqdn'] = $this->option('fqdn') ?? $this->ask('Enter a domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node'); + $data['internal_fqdn'] = $this->option('internal-fqdn') ?? $this->ask('Enter internal FQDN for panel-to-Wings communication (leave blank to use public FQDN)', ''); $data['public'] = $this->option('public') ?? $this->confirm('Should this node be public? As a note, setting a node to private you will be denying the ability to auto-deploy to this node.', true); $data['behind_proxy'] = $this->option('proxy') ?? $this->confirm('Is your FQDN behind a proxy?'); $data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm('Should maintenance mode be enabled?'); diff --git a/app/Contracts/Captcha/CaptchaProviderInterface.php b/app/Contracts/Captcha/CaptchaProviderInterface.php new file mode 100644 index 000000000..e42257f52 --- /dev/null +++ b/app/Contracts/Captcha/CaptchaProviderInterface.php @@ -0,0 +1,41 @@ +level; + } + + public function getStatusCode(): int + { + return Response::HTTP_BAD_REQUEST; + } + + public function getHeaders(): array + { + return []; + } + + /** + * Render the exception to the user by adding a flashed message to the session + * and then redirecting them back to the page that they came from. If the + * request originated from an API hit, return the error in JSONAPI spec format. + */ + public function render(Request $request): JsonResponse|RedirectResponse + { + if ($request->expectsJson()) { + return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders()); } - public function getErrorLevel(): string - { - return $this->level; + app(AlertsMessageBag::class)->danger($this->getMessage())->flash(); + + return redirect()->back()->withInput(); + } + + /** + * Log the exception to the logs using the defined error level only if the previous + * exception is set. + * + * @throws \Throwable + */ + public function report() + { + if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) { + return null; } - public function getStatusCode(): int - { - return Response::HTTP_BAD_REQUEST; + try { + $logger = Container::getInstance()->make(LoggerInterface::class); + } catch (\Exception) { + throw $this->getPrevious(); } - public function getHeaders(): array - { - return []; - } - - /** - * Render the exception to the user by adding a flashed message to the session - * and then redirecting them back to the page that they came from. If the - * request originated from an API hit, return the error in JSONAPI spec format. - */ - public function render(Request $request): JsonResponse|RedirectResponse - { - if ($request->expectsJson()) { - return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders()); - } - - app(AlertsMessageBag::class)->danger($this->getMessage())->flash(); - - return redirect()->back()->withInput(); - } - - /** - * Log the exception to the logs using the defined error level only if the previous - * exception is set. - * - * @throws \Throwable - */ - public function report() - { - if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) { - return null; - } - - try { - $logger = Container::getInstance()->make(LoggerInterface::class); - } catch (\Exception) { - throw $this->getPrevious(); - } - - return $logger->{$this->getErrorLevel()}($this->getPrevious()); - } + return $logger->{$this->getErrorLevel()}($this->getPrevious()); + } } diff --git a/app/Exceptions/Dns/DnsProviderException.php b/app/Exceptions/Dns/DnsProviderException.php new file mode 100644 index 000000000..2c144b08a --- /dev/null +++ b/app/Exceptions/Dns/DnsProviderException.php @@ -0,0 +1,94 @@ +with('location')->withCount('servers') - ) - ->allowedFilters(['uuid', 'name']) - ->allowedSorts(['id']) - ->paginate(25); + /** + * Returns a listing of nodes on the system. + */ + public function index(Request $request): View + { + $nodes = QueryBuilder::for( + Node::query()->with('location')->withCount('servers') + ) + ->allowedFilters(['uuid', 'name']) + ->allowedSorts(['id']) + ->paginate(25); - return $this->view->make('admin.nodes.index', ['nodes' => $nodes]); - } + return $this->view->make('admin.nodes.index', ['nodes' => $nodes]); + } } diff --git a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php index 875b1afbe..72a86c225 100644 --- a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php +++ b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php @@ -11,30 +11,30 @@ use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; class SystemInformationController extends Controller { - /** - * SystemInformationController constructor. - */ - public function __construct(private DaemonConfigurationRepository $repository) - { - } + /** + * SystemInformationController constructor. + */ + public function __construct(private DaemonConfigurationRepository $repository) + { + } - /** - * Returns system information from the Daemon. - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function __invoke(Request $request, Node $node): JsonResponse - { - $data = $this->repository->setNode($node)->getSystemInformation(); + /** + * Returns system information from the Daemon. + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function __invoke(Request $request, Node $node): JsonResponse + { + $data = $this->repository->setNode($node)->getSystemInformation(); - return new JsonResponse([ - 'version' => $data['version'] ?? '', - 'system' => [ - 'type' => Str::title($data['os'] ?? 'Unknown'), - 'arch' => $data['architecture'] ?? '--', - 'release' => $data['kernel_version'] ?? '--', - 'cpus' => $data['cpu_count'] ?? 0, - ], - ]); - } + return new JsonResponse([ + 'version' => $data['version'] ?? '', + 'system' => [ + 'type' => Str::title($data['os'] ?? 'Unknown'), + 'arch' => $data['architecture'] ?? '--', + 'release' => $data['kernel_version'] ?? '--', + 'cpus' => $data['cpu_count'] ?? 0, + ], + ]); + } } diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index a641310e1..b71493292 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -147,7 +147,7 @@ class NodesController extends Controller { $this->allocationRepository->update($request->input('allocation_id'), [ 'ip_alias' => (empty($request->input('alias'))) ? null : $request->input('alias'), - ]); + ], false); // Skip validation return response('', 204); } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 1805a831a..f5a77cbd6 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -144,8 +144,9 @@ class ServersController extends Controller try { $this->buildModificationService->handle($server, $request->only([ 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', + 'memory', 'overhead_memory', 'swap', 'io', 'cpu', 'threads', 'disk', 'database_limit', 'allocation_limit', 'backup_limit', 'oom_disabled', + 'exclude_from_resource_calculation', ])); } catch (DataValidationException $exception) { throw new ValidationException($exception->getValidator()); diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php index 9033e7b92..de58f3cf3 100644 --- a/app/Http/Controllers/Admin/Settings/AdvancedController.php +++ b/app/Http/Controllers/Admin/Settings/AdvancedController.php @@ -14,49 +14,41 @@ use Pterodactyl\Http\Requests\Admin\Settings\AdvancedSettingsFormRequest; class AdvancedController extends Controller { - /** - * AdvancedController constructor. - */ - public function __construct( - private AlertsMessageBag $alert, - private ConfigRepository $config, - private Kernel $kernel, - private SettingsRepositoryInterface $settings, - private ViewFactory $view, - ) { + /** + * AdvancedController constructor. + */ + public function __construct( + private AlertsMessageBag $alert, + private ConfigRepository $config, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, + private ViewFactory $view, + ) { + } + + /** + * Render advanced Panel settings UI. + */ + public function index(): View + { + return $this->view->make('admin.settings.advanced'); + } + + /** + * Update advanced settings. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(AdvancedSettingsFormRequest $request): RedirectResponse + { + foreach ($request->normalize() as $key => $value) { + $this->settings->set('settings::' . $key, $value); } - /** - * Render advanced Panel settings UI. - */ - public function index(): View - { - $showRecaptchaWarning = false; - if ( - $this->config->get('recaptcha._shipped_secret_key') === $this->config->get('recaptcha.secret_key') - || $this->config->get('recaptcha._shipped_website_key') === $this->config->get('recaptcha.website_key') - ) { - $showRecaptchaWarning = true; - } + $this->kernel->call('queue:restart'); + $this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - return $this->view->make('admin.settings.advanced', [ - 'showRecaptchaWarning' => $showRecaptchaWarning, - ]); - } - - /** - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(AdvancedSettingsFormRequest $request): RedirectResponse - { - foreach ($request->normalize() as $key => $value) { - $this->settings->set('settings::' . $key, $value); - } - - $this->kernel->call('queue:restart'); - $this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - - return redirect()->route('admin.settings.advanced'); - } -} + return redirect()->route('admin.settings.advanced'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/Settings/CaptchaController.php b/app/Http/Controllers/Admin/Settings/CaptchaController.php new file mode 100644 index 000000000..5072be2b5 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/CaptchaController.php @@ -0,0 +1,69 @@ +view->make('admin.settings.captcha', [ + 'providers' => [ + 'none' => 'Disabled', + 'turnstile' => 'Cloudflare Turnstile', + 'hcaptcha' => 'hCaptcha', + 'recaptcha' => 'Google reCAPTCHA', + ], + ]); + } + + /** + * Update captcha settings. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(CaptchaSettingsFormRequest $request): RedirectResponse + { + $values = $request->normalize(); + + foreach ($values as $key => $value) { + // Encrypt secret keys before storing + if (in_array($key, \Pterodactyl\Providers\SettingsServiceProvider::getEncryptedKeys()) && !empty($value)) { + $value = $this->encrypter->encrypt($value); + } + + $this->settings->set('settings::' . $key, $value); + } + + $this->kernel->call('queue:restart'); + $this->alert->success('Captcha settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); + + return redirect()->route('admin.settings.captcha'); + } +} diff --git a/app/Http/Controllers/Admin/Settings/DomainsController.php b/app/Http/Controllers/Admin/Settings/DomainsController.php new file mode 100644 index 000000000..ba2ab6924 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/DomainsController.php @@ -0,0 +1,247 @@ +orderBy('created_at', 'desc')->get(); + $availableProviders = $this->getAvailableProviders(); + + return $this->view->make('admin.settings.domains.index', [ + 'domains' => $domains, + 'providers' => $availableProviders, + ]); + } + + /** + * Show the form for creating a new domain. + */ + public function create(): View + { + $availableProviders = $this->getAvailableProviders(); + + return $this->view->make('admin.settings.domains.create', [ + 'providers' => $availableProviders, + ]); + } + + /** + * Store a newly created domain. + */ + public function store(DomainFormRequest $request): RedirectResponse + { + $data = $request->validated(); + + try { + // Test the DNS provider connection + $providerClass = $this->getProviderClass($data['dns_provider']); + $provider = new $providerClass($data['dns_config']); + $provider->testConnection(); + + // Handle domain creation in a transaction + \DB::transaction(function () use ($data) { + // If this domain is being set as default, remove default from other domains + if (!empty($data['is_default'])) { + Domain::where('is_default', true)->update(['is_default' => false]); + } + + // Create the domain + Domain::create([ + 'name' => $data['name'], + 'dns_provider' => $data['dns_provider'], + 'dns_config' => $data['dns_config'], + 'is_active' => $data['is_active'] ?? true, + 'is_default' => $data['is_default'] ?? false, + ]); + }); + + return redirect()->route('admin.settings.domains.index') + ->with('success', 'Domain created successfully.'); + } catch (DnsProviderException $e) { + return back()->withInput()->withErrors(['dns_config' => $e->getMessage()]); + } catch (\Exception $e) { + return back()->withInput()->withErrors(['general' => 'Failed to create domain: ' . $e->getMessage()]); + } + } + + /** + * Show the form for editing a domain. + */ + public function edit(Domain $domain): View + { + $domain->load('serverSubdomains'); + $availableProviders = $this->getAvailableProviders(); + + return $this->view->make('admin.settings.domains.edit', [ + 'domain' => $domain, + 'providers' => $availableProviders, + ]); + } + + /** + * Update the specified domain. + */ + public function update(DomainFormRequest $request, Domain $domain): RedirectResponse + { + $data = $request->validated(); + + try { + // Test the DNS provider connection if config changed + if ($data['dns_config'] !== $domain->dns_config || $data['dns_provider'] !== $domain->dns_provider) { + $providerClass = $this->getProviderClass($data['dns_provider']); + $provider = new $providerClass($data['dns_config']); + $provider->testConnection(); + } + + // Handle domain update in a transaction + \DB::transaction(function () use ($data, $domain) { + // Handle default domain changes + $newIsDefault = $data['is_default'] ?? false; + if ($newIsDefault && !$domain->is_default) { + // If this domain is being set as default, remove default from other domains + Domain::where('is_default', true)->update(['is_default' => false]); + } elseif (!$newIsDefault && $domain->is_default) { + // Don't allow removing default status if this is the only default domain + $defaultCount = Domain::where('is_default', true)->count(); + if ($defaultCount <= 1) { + throw new \Exception('Cannot remove default status: At least one domain must be set as default.'); + } + } + + // Update the domain + $domain->update([ + 'name' => $data['name'], + 'dns_provider' => $data['dns_provider'], + 'dns_config' => $data['dns_config'], + 'is_active' => $data['is_active'] ?? $domain->is_active, + 'is_default' => $newIsDefault, + ]); + }); + + return redirect()->route('admin.settings.domains.index') + ->with('success', 'Domain updated successfully.'); + } catch (DnsProviderException $e) { + return back()->withInput()->withErrors(['dns_config' => $e->getMessage()]); + } catch (\Exception $e) { + return back()->withInput()->withErrors(['general' => 'Failed to update domain: ' . $e->getMessage()]); + } + } + + /** + * Remove the specified domain. + */ + public function destroy(Domain $domain): RedirectResponse + { + try { + // Check if domain has active subdomains + $activeSubdomains = $domain->activeSubdomains()->count(); + if ($activeSubdomains > 0) { + return back()->withErrors(['general' => "Cannot delete domain with {$activeSubdomains} active subdomains."]); + } + + // Don't allow deleting the only default domain + if ($domain->is_default) { + $defaultCount = Domain::where('is_default', true)->count(); + if ($defaultCount <= 1) { + return back()->withErrors(['general' => 'Cannot delete the only default domain. Please set another domain as default first.']); + } + } + + $domain->delete(); + + return redirect()->route('admin.settings.domains.index') + ->with('success', 'Domain deleted successfully.'); + } catch (\Exception $e) { + return back()->withErrors(['general' => 'Failed to delete domain: ' . $e->getMessage()]); + } + } + + /** + * Test the connection to a DNS provider. + */ + public function testConnection(Request $request): \Illuminate\Http\JsonResponse + { + $request->validate([ + 'dns_provider' => 'required|string', + 'dns_config' => 'required|array', + ]); + + try { + $providerClass = $this->getProviderClass($request->input('dns_provider')); + $provider = new $providerClass($request->input('dns_config')); + $provider->testConnection(); + + return response()->json(['success' => true, 'message' => 'Connection successful.']); + } catch (DnsProviderException $e) { + return response()->json(['success' => false, 'message' => $e->getMessage()], 400); + } catch (\Exception $e) { + return response()->json(['success' => false, 'message' => 'Connection failed: ' . $e->getMessage()], 500); + } + } + + /** + * Get configuration schema for a DNS provider. + */ + public function getProviderSchema(string $provider): \Illuminate\Http\JsonResponse + { + try { + $providerClass = $this->getProviderClass($provider); + $providerInstance = new $providerClass([]); + $schema = $providerInstance->getConfigurationSchema(); + + return response()->json(['success' => true, 'schema' => $schema]); + } catch (\Exception $e) { + return response()->json(['success' => false, 'message' => $e->getMessage()], 400); + } + } + + /** + * Get available DNS providers. + */ + private function getAvailableProviders(): array + { + return [ + 'cloudflare' => [ + 'name' => 'Cloudflare', + 'description' => 'Cloudflare DNS service', + ], + ]; + } + + /** + * Get the provider class for a given provider name. + */ + private function getProviderClass(string $provider): string + { + $providers = [ + 'cloudflare' => \Pterodactyl\Services\Dns\Providers\CloudflareProvider::class, + ]; + + if (!isset($providers[$provider])) { + throw new \Exception("Unsupported DNS provider: {$provider}"); + } + + return $providers[$provider]; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php index f45a46404..b7998adc7 100644 --- a/app/Http/Controllers/Admin/Settings/IndexController.php +++ b/app/Http/Controllers/Admin/Settings/IndexController.php @@ -15,46 +15,46 @@ use Pterodactyl\Http\Requests\Admin\Settings\BaseSettingsFormRequest; class IndexController extends Controller { - use AvailableLanguages; + use AvailableLanguages; - /** - * IndexController constructor. - */ - public function __construct( - private AlertsMessageBag $alert, - private Kernel $kernel, - private SettingsRepositoryInterface $settings, - private SoftwareVersionService $versionService, - private ViewFactory $view, - ) { + /** + * IndexController constructor. + */ + public function __construct( + private AlertsMessageBag $alert, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, + private SoftwareVersionService $versionService, + private ViewFactory $view, + ) { + } + + /** + * Render the UI for basic Panel settings. + */ + public function index(): View + { + return $this->view->make('admin.settings.index', [ + 'version' => $this->versionService, + 'languages' => $this->getAvailableLanguages(true), + ]); + } + + /** + * Handle settings update. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(BaseSettingsFormRequest $request): RedirectResponse + { + foreach ($request->normalize() as $key => $value) { + $this->settings->set('settings::' . $key, $value); } - /** - * Render the UI for basic Panel settings. - */ - public function index(): View - { - return $this->view->make('admin.settings.index', [ - 'version' => $this->versionService, - 'languages' => $this->getAvailableLanguages(true), - ]); - } + $this->kernel->call('queue:restart'); + $this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - /** - * Handle settings update. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(BaseSettingsFormRequest $request): RedirectResponse - { - foreach ($request->normalize() as $key => $value) { - $this->settings->set('settings::' . $key, $value); - } - - $this->kernel->call('queue:restart'); - $this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - - return redirect()->route('admin.settings'); - } + return redirect()->route('admin.settings'); + } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index 5c40f77f6..de969ef27 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -18,90 +18,90 @@ use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; class NodeController extends ApplicationApiController { - /** - * NodeController constructor. - */ - public function __construct( - private NodeCreationService $creationService, - private NodeDeletionService $deletionService, - private NodeUpdateService $updateService, - ) { - parent::__construct(); - } + /** + * NodeController constructor. + */ + public function __construct( + private NodeCreationService $creationService, + private NodeDeletionService $deletionService, + private NodeUpdateService $updateService, + ) { + parent::__construct(); + } - /** - * Return all the nodes currently available on the Panel. - */ - public function index(GetNodesRequest $request): array - { - $nodes = QueryBuilder::for(Node::query()) - ->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id']) - ->allowedSorts(['id', 'uuid', 'memory', 'disk']) - ->paginate($request->query('per_page') ?? 50); + /** + * Return all the nodes currently available on the Panel. + */ + public function index(GetNodesRequest $request): array + { + $nodes = QueryBuilder::for(Node::query()) + ->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id']) + ->allowedSorts(['id', 'uuid', 'memory', 'disk']) + ->paginate($request->query('per_page') ?? 50); - return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) - ->toArray(); - } + return $this->fractal->collection($nodes) + ->transformWith($this->getTransformer(NodeTransformer::class)) + ->toArray(); + } - /** - * Return data for a single instance of a node. - */ - public function view(GetNodeRequest $request, Node $node): array - { - return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) - ->toArray(); - } + /** + * Return data for a single instance of a node. + */ + public function view(GetNodeRequest $request, Node $node): array + { + return $this->fractal->item($node) + ->transformWith($this->getTransformer(NodeTransformer::class)) + ->toArray(); + } - /** - * Create a new node on the Panel. Returns the created node and an HTTP/201 - * status response on success. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function store(StoreNodeRequest $request): JsonResponse - { - $node = $this->creationService->handle($request->validated()); + /** + * Create a new node on the Panel. Returns the created node and an HTTP/201 + * status response on success. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreNodeRequest $request): JsonResponse + { + $node = $this->creationService->handle($request->validated()); - return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.nodes.view', [ - 'node' => $node->id, - ]), - ]) - ->respond(201); - } + return $this->fractal->item($node) + ->transformWith($this->getTransformer(NodeTransformer::class)) + ->addMeta([ + 'resource' => route('api.application.nodes.view', [ + 'node' => $node->id, + ]), + ]) + ->respond(201); + } - /** - * Update an existing node on the Panel. - * - * @throws \Throwable - */ - public function update(UpdateNodeRequest $request, Node $node): array - { - $node = $this->updateService->handle( - $node, - $request->validated(), - $request->input('reset_secret') === true - ); + /** + * Update an existing node on the Panel. + * + * @throws \Throwable + */ + public function update(UpdateNodeRequest $request, Node $node): array + { + $node = $this->updateService->handle( + $node, + $request->validated(), + $request->input('reset_secret') === true + ); - return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) - ->toArray(); - } + return $this->fractal->item($node) + ->transformWith($this->getTransformer(NodeTransformer::class)) + ->toArray(); + } - /** - * Deletes a given node from the Panel as long as there are no servers - * currently attached to it. - * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - */ - public function delete(DeleteNodeRequest $request, Node $node): JsonResponse - { - $this->deletionService->handle($node); + /** + * Deletes a given node from the Panel as long as there are no servers + * currently attached to it. + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function delete(DeleteNodeRequest $request, Node $node): JsonResponse + { + $this->deletionService->handle($node); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); - } + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } } diff --git a/app/Http/Controllers/Api/Client/Nests/EggController.php b/app/Http/Controllers/Api/Client/Nests/EggController.php index ef8776692..49c354e9b 100644 --- a/app/Http/Controllers/Api/Client/Nests/EggController.php +++ b/app/Http/Controllers/Api/Client/Nests/EggController.php @@ -4,12 +4,12 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Nests; use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; -use Pterodactyl\Transformers\Api\Client\EggTransformer; +use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest; -use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; +use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; -class EggController extends ClientApiController +class EggController extends ApplicationApiController { /** * Return all eggs that exist for a given nest. diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php index d11187c7e..66c7bd46f 100644 --- a/app/Http/Controllers/Api/Client/Servers/BackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -13,6 +13,7 @@ use Pterodactyl\Services\Backups\DeleteBackupService; use Pterodactyl\Services\Backups\DownloadLinkService; use Pterodactyl\Repositories\Eloquent\BackupRepository; use Pterodactyl\Services\Backups\InitiateBackupService; +use Pterodactyl\Services\Backups\ServerStateService; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Transformers\Api\Client\BackupTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -22,203 +23,334 @@ use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\RestoreBackupRequest; class BackupController extends ClientApiController { - /** - * BackupController constructor. - */ - public function __construct( - private DaemonBackupRepository $daemonRepository, - private DeleteBackupService $deleteBackupService, - private InitiateBackupService $initiateBackupService, - private DownloadLinkService $downloadLinkService, - private BackupRepository $repository, - ) { - parent::__construct(); + /** + * BackupController constructor. + */ + public function __construct( + private DaemonBackupRepository $daemonRepository, + private DeleteBackupService $deleteBackupService, + private InitiateBackupService $initiateBackupService, + private DownloadLinkService $downloadLinkService, + private BackupRepository $repository, + private ServerStateService $serverStateService, + ) { + parent::__construct(); + } + + /** + * Returns all the backups for a given server instance in a paginated + * result set. + * + * @throws AuthorizationException + */ + public function index(Request $request, Server $server): array + { + if (!$request->user()->can(Permission::ACTION_BACKUP_READ, $server)) { + throw new AuthorizationException(); } - /** - * Returns all the backups for a given server instance in a paginated - * result set. - * - * @throws AuthorizationException - */ - public function index(Request $request, Server $server): array - { - if (!$request->user()->can(Permission::ACTION_BACKUP_READ, $server)) { - throw new AuthorizationException(); - } + $limit = min($request->query('per_page') ?? 20, 50); - $limit = min($request->query('per_page') ?? 20, 50); + // Sort backups: locked ones first, then by created_at descending (latest first) + $backups = $server->backups() + ->orderByRaw('is_locked DESC, created_at DESC') + ->paginate($limit); - return $this->fractal->collection($server->backups()->paginate($limit)) - ->transformWith($this->getTransformer(BackupTransformer::class)) - ->addMeta([ - 'backup_count' => $this->repository->getNonFailedBackups($server)->count(), - ]) - ->toArray(); + return $this->fractal->collection($backups) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->addMeta([ + 'backup_count' => $this->repository->getNonFailedBackups($server)->count(), + ]) + ->toArray(); + } + + /** + * Starts the backup process for a server. + * + * @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation + * @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified + * @throws \Throwable + */ + public function store(StoreBackupRequest $request, Server $server): array + { + $action = $this->initiateBackupService + ->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? '')); + + // Only set the lock status if the user even has permission to delete backups, + // otherwise ignore this status. This gets a little funky since it isn't clear + // how best to allow a user to create a backup that is locked without also preventing + // them from just filling up a server with backups that can never be deleted? + if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { + $action->setIsLocked((bool) $request->input('is_locked')); } - /** - * Starts the backup process for a server. - * - * @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation - * @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified - * @throws \Throwable - */ - public function store(StoreBackupRequest $request, Server $server): array - { - $action = $this->initiateBackupService - ->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? '')); + $backup = $action->handle($server, $request->input('name')); - // Only set the lock status if the user even has permission to delete backups, - // otherwise ignore this status. This gets a little funky since it isn't clear - // how best to allow a user to create a backup that is locked without also preventing - // them from just filling up a server with backups that can never be deleted? - if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { - $action->setIsLocked((bool) $request->input('is_locked')); - } + Activity::event('server:backup.start') + ->subject($backup) + ->property(['name' => $backup->name, 'locked' => (bool) $request->input('is_locked')]) + ->log(); - $backup = $action->handle($server, $request->input('name')); + return $this->fractal->item($backup) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } - Activity::event('server:backup.start') - ->subject($backup) - ->property(['name' => $backup->name, 'locked' => (bool) $request->input('is_locked')]) - ->log(); - - return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) - ->toArray(); + /** + * Toggles the lock status of a given backup for a server. + * + * @throws \Throwable + * @throws AuthorizationException + */ + public function toggleLock(Request $request, Server $server, Backup $backup): array + { + if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { + throw new AuthorizationException(); } - /** - * Toggles the lock status of a given backup for a server. - * - * @throws \Throwable - * @throws AuthorizationException - */ - public function toggleLock(Request $request, Server $server, Backup $backup): array - { - if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { - throw new AuthorizationException(); - } + $action = $backup->is_locked ? 'server:backup.unlock' : 'server:backup.lock'; - $action = $backup->is_locked ? 'server:backup.unlock' : 'server:backup.lock'; + $backup->update(['is_locked' => !$backup->is_locked]); - $backup->update(['is_locked' => !$backup->is_locked]); + Activity::event($action)->subject($backup)->property('name', $backup->name)->log(); - Activity::event($action)->subject($backup)->property('name', $backup->name)->log(); + return $this->fractal->item($backup) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } - return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) - ->toArray(); + /** + * Rename a backup. + * + * @throws AuthorizationException + */ + public function rename(Request $request, Server $server, Backup $backup): array + { + if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { + throw new AuthorizationException(); } - /** - * Returns information about a single backup. - * - * @throws AuthorizationException - */ - public function view(Request $request, Server $server, Backup $backup): array - { - if (!$request->user()->can(Permission::ACTION_BACKUP_READ, $server)) { - throw new AuthorizationException(); - } + $request->validate([ + 'name' => 'required|string|min:1|max:191', + ]); - return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) - ->toArray(); + $oldName = $backup->name; + $newName = trim($request->input('name')); + + // Sanitize backup name to prevent injection + $newName = preg_replace('/[^a-zA-Z0-9\s\-_\.\(\)→:,]/', '', $newName); + $newName = substr($newName, 0, 191); // Limit to database field length + + if (empty($newName)) { + throw new BadRequestHttpException('Backup name cannot be empty after sanitization.'); } - /** - * Deletes a backup from the panel as well as the remote source where it is currently - * being stored. - * - * @throws \Throwable - */ - public function delete(Request $request, Server $server, Backup $backup): JsonResponse - { - if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { - throw new AuthorizationException(); - } + $backup->update(['name' => $newName]); - $this->deleteBackupService->handle($backup); + Activity::event('server:backup.rename') + ->subject($backup) + ->property([ + 'old_name' => $oldName, + 'new_name' => $newName, + ]) + ->log(); - Activity::event('server:backup.delete') - ->subject($backup) - ->property(['name' => $backup->name, 'failed' => !$backup->is_successful]) - ->log(); + return $this->fractal->item($backup) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + /** + * Returns information about a single backup. + * + * @throws AuthorizationException + */ + public function view(Request $request, Server $server, Backup $backup): array + { + if (!$request->user()->can(Permission::ACTION_BACKUP_READ, $server)) { + throw new AuthorizationException(); } - /** - * Download the backup for a given server instance. For daemon local files, the file - * will be streamed back through the Panel. For AWS S3 files, a signed URL will be generated - * which the user is redirected to. - * - * @throws \Throwable - * @throws AuthorizationException - */ - public function download(Request $request, Server $server, Backup $backup): JsonResponse - { - if (!$request->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server)) { - throw new AuthorizationException(); - } + return $this->fractal->item($backup) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } - if ($backup->disk !== Backup::ADAPTER_AWS_S3 && $backup->disk !== Backup::ADAPTER_WINGS) { - throw new BadRequestHttpException('The backup requested references an unknown disk driver type and cannot be downloaded.'); - } + /** + * Deletes a backup from the panel as well as the remote source where it is currently + * being stored. + * + * @throws \Throwable + */ + public function delete(Request $request, Server $server, Backup $backup): JsonResponse + { + if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { + throw new AuthorizationException(); + } - $url = $this->downloadLinkService->handle($backup, $request->user()); + $this->deleteBackupService->handle($backup); - Activity::event('server:backup.download')->subject($backup)->property('name', $backup->name)->log(); + Activity::event('server:backup.delete') + ->subject($backup) + ->property(['name' => $backup->name, 'failed' => !$backup->is_successful]) + ->log(); - return new JsonResponse([ - 'object' => 'signed_url', - 'attributes' => ['url' => $url], + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } + + /** + * Download the backup for a given server instance. For daemon local files, the file + * will be streamed back through the Panel. For AWS S3 files, a signed URL will be generated + * which the user is redirected to. + * + * @throws \Throwable + * @throws AuthorizationException + */ + public function download(Request $request, Server $server, Backup $backup): JsonResponse + { + if (!$request->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server)) { + throw new AuthorizationException(); + } + + if ($backup->disk !== Backup::ADAPTER_AWS_S3 && $backup->disk !== Backup::ADAPTER_WINGS) { + throw new BadRequestHttpException('The backup requested references an unknown disk driver type and cannot be downloaded.'); + } + + $url = $this->downloadLinkService->handle($backup, $request->user()); + + Activity::event('server:backup.download')->subject($backup)->property('name', $backup->name)->log(); + + return new JsonResponse([ + 'object' => 'signed_url', + 'attributes' => ['url' => $url], + ]); + } + + /** + * Handles restoring a backup by making a request to the Wings instance telling it + * to begin the process of finding (or downloading) the backup and unpacking it + * over the server files. + * + * All files that currently exist on the server will be deleted before restoring + * the backup to ensure a clean restoration process. + * + * @throws \Throwable + */ + public function restore(RestoreBackupRequest $request, Server $server, Backup $backup): JsonResponse + { + $this->validateServerForRestore($server); + + $this->validateBackupForRestore($backup); + + // Validate server state compatibility if backup has state data + if ($this->serverStateService->hasServerState($backup)) { + $compatibility = $this->serverStateService->validateRestoreCompatibility($backup); + + if (!empty($compatibility['errors'])) { + throw new BadRequestHttpException('Cannot restore backup: ' . implode(' ', $compatibility['errors'])); + } + + // Log warnings for user awareness + if (!empty($compatibility['warnings'])) { + \Log::warning('Backup restore compatibility warnings', [ + 'backup_uuid' => $backup->uuid, + 'server_uuid' => $server->uuid, + 'warnings' => $compatibility['warnings'], ]); + } } - /** - * Handles restoring a backup by making a request to the Wings instance telling it - * to begin the process of finding (or downloading) the backup and unpacking it - * over the server files. - * - * If the "truncate" flag is passed through in this request then all the - * files that currently exist on the server will be deleted before restoring. - * Otherwise, the archive will simply be unpacked over the existing files. - * - * @throws \Throwable - */ - public function restore(RestoreBackupRequest $request, Server $server, Backup $backup): JsonResponse - { - // Cannot restore a backup unless a server is fully installed and not currently - // processing a different backup restoration request. - if (!is_null($server->status)) { - throw new BadRequestHttpException('This server is not currently in a state that allows for a backup to be restored.'); + $hasServerState = $this->serverStateService->hasServerState($backup); + + $log = Activity::event('server:backup.restore') + ->subject($backup) + ->property([ + 'name' => $backup->name, + 'truncate' => true, + 'has_server_state' => $hasServerState, + ]); + + $log->transaction(function () use ($backup, $server, $request, $hasServerState) { + // Double-check server state within transaction to prevent race conditions + $server->refresh(); + if (!is_null($server->status)) { + throw new BadRequestHttpException('Server state changed during restore initiation. Please try again.'); + } + + // If the backup is for an S3 file we need to generate a unique Download link for + // it that will allow Wings to actually access the file. + $url = null; + if ($backup->disk === Backup::ADAPTER_AWS_S3) { + try { + $url = $this->downloadLinkService->handle($backup, $request->user()); + } catch (\Exception $e) { + throw new BadRequestHttpException('Failed to generate download link for S3 backup: ' . $e->getMessage()); } + } - if (!$backup->is_successful && is_null($backup->completed_at)) { - throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.'); + // Update the status right away for the server so that we know not to allow certain + // actions against it via the Panel API. + $server->update(['status' => Server::STATUS_RESTORING_BACKUP]); + + try { + // Start the file restoration process on Wings (always truncate for clean restore) + $this->daemonRepository->setServer($server)->restore($backup, $url); + + // If backup has server state, restore it immediately + // This is safe to do now since we're in a transaction and the daemon request succeeded + if ($hasServerState) { + $this->serverStateService->restoreServerState($server, $backup); } + } catch (\Exception $e) { + // If either daemon request or state restoration fails, reset server status + $server->update(['status' => null]); + throw $e; + } + }); - $log = Activity::event('server:backup.restore') - ->subject($backup) - ->property(['name' => $backup->name, 'truncate' => $request->input('truncate')]); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } - $log->transaction(function () use ($backup, $server, $request) { - // If the backup is for an S3 file we need to generate a unique Download link for - // it that will allow Wings to actually access the file. - if ($backup->disk === Backup::ADAPTER_AWS_S3) { - $url = $this->downloadLinkService->handle($backup, $request->user()); - } - - // Update the status right away for the server so that we know not to allow certain - // actions against it via the Panel API. - $server->update(['status' => Server::STATUS_RESTORING_BACKUP]); - - $this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate')); - }); - - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + /** + * Validate server state for backup restoration + */ + private function validateServerForRestore(Server $server): void + { + // Cannot restore a backup unless a server is fully installed and not currently + // processing a different backup restoration request. + if (!is_null($server->status)) { + throw new BadRequestHttpException('This server is not currently in a state that allows for a backup to be restored.'); } + + if ($server->isSuspended()) { + throw new BadRequestHttpException('Cannot restore backup for suspended server.'); + } + + if (!$server->isInstalled()) { + throw new BadRequestHttpException('Cannot restore backup for server that is not fully installed.'); + } + + if ($server->transfer) { + throw new BadRequestHttpException('Cannot restore backup while server is being transferred.'); + } + } + + /** + * Validate backup for restoration + */ + private function validateBackupForRestore(Backup $backup): void + { + if (!$backup->is_successful && is_null($backup->completed_at)) { + throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.'); + } + + // Additional safety check for backup integrity + if (!$backup->is_successful) { + throw new BadRequestHttpException('Cannot restore a failed backup.'); + } + + if (is_null($backup->completed_at)) { + throw new BadRequestHttpException('Cannot restore backup that is still in progress.'); + } + } } diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 5cf774ae6..2068dd0e3 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -50,7 +50,7 @@ class NetworkAllocationController extends ClientApiController { $original = $allocation->notes; - $allocation->forceFill(['notes' => $request->input('notes')])->save(); + $allocation->forceFill(['notes' => $request->input('notes')])->skipValidation()->save(); if ($original !== $allocation->notes) { Activity::event('server:allocation.notes') diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index f662c1c5d..376739d9d 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -2,37 +2,45 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; +use Exception; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\Log; use Pterodactyl\Facades\Activity; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\ReinstallServerService; +use Pterodactyl\Services\Backups\InitiateBackupService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; +use Pterodactyl\Services\ServerOperations\ServerOperationService; +use Pterodactyl\Services\ServerOperations\ServerStateValidationService; +use Pterodactyl\Services\ServerOperations\EggChangeService; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\SetDockerImageRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RevertDockerImageRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\SetEggRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\PreviewEggRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\ApplyEggChangeRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\ReinstallServerRequest; +use Pterodactyl\Services\Servers\StartupModificationService; +use Pterodactyl\Repositories\Wings\DaemonFileRepository; class SettingsController extends ClientApiController { - /** - * SettingsController constructor. - */ public function __construct( private ServerRepository $repository, private ReinstallServerService $reinstallServerService, + private StartupModificationService $startupModificationService, + private InitiateBackupService $backupService, + private DaemonFileRepository $fileRepository, + private ServerOperationService $operationService, + private ServerStateValidationService $validationService, + private EggChangeService $eggChangeService, ) { parent::__construct(); } - /** - * Renames a server. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ public function rename(RenameServerRequest $request, Server $server): JsonResponse { $name = $request->input('name'); @@ -57,25 +65,13 @@ class SettingsController extends ClientApiController return new JsonResponse([], Response::HTTP_NO_CONTENT); } - /** - * Reinstalls the server on the daemon. - * - * @throws \Throwable - */ public function reinstall(ReinstallServerRequest $request, Server $server): JsonResponse { $this->reinstallServerService->handle($server); - Activity::event('server:reinstall')->log(); - return new JsonResponse([], Response::HTTP_ACCEPTED); } - /** - * Changes the Docker image in use by the server. - * - * @throws \Throwable - */ public function dockerImage(SetDockerImageRequest $request, Server $server): JsonResponse { if (!in_array($request->input('docker_image'), array_values($server->egg->docker_images))) { @@ -94,44 +90,150 @@ class SettingsController extends ClientApiController return new JsonResponse([], Response::HTTP_NO_CONTENT); } - /** - * Reset Startup Command - */ + public function revertDockerImage(RevertDockerImageRequest $request, Server $server): JsonResponse + { + $server->validateCurrentState(); + + $original = $server->image; + $defaultImage = $server->getDefaultDockerImage(); + + if (empty($defaultImage)) { + throw new BadRequestHttpException('No default docker image available for this server\'s egg.'); + } + + $server->forceFill(['image' => $defaultImage])->saveOrFail(); + + Activity::event('server:startup.image.reverted') + ->property([ + 'old' => $original, + 'new' => $defaultImage, + 'reverted_to_egg_default' => true, + ]) + ->log(); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + private function resetStartupCommand(Server $server): JsonResponse { $server->startup = $server->egg->startup; $server->save(); - return new JsonResponse([], Response::HTTP_NO_CONTENT); } - /** - * Changes the egg for a server. - * - * @throws \Throwable - */ - public function changeEgg(SetEggRequest $request, Server $server): JsonResponse { + public function changeEgg(SetEggRequest $request, Server $server): JsonResponse + { $eggId = $request->input('egg_id'); $nestId = $request->input('nest_id'); $originalEggId = $server->egg_id; $originalNestId = $server->nest_id; - - // Check if the new Egg and Nest IDs are different from the current ones + if ($originalEggId !== $eggId || $originalNestId !== $nestId) { - // Update the server's Egg and Nest IDs $server->egg_id = $eggId; $server->nest_id = $nestId; $server->save(); - - // Log an activity event for the Egg change + Activity::event('server:settings.egg') ->property(['original_egg_id' => $originalEggId, 'new_egg_id' => $eggId, 'original_nest_id' => $originalNestId, 'new_nest_id' => $nestId]) ->log(); - // Reset the server's startup command $this->resetStartupCommand($server); } - + return new JsonResponse([], Response::HTTP_NO_CONTENT); } + public function previewEggChange(PreviewEggRequest $request, Server $server): JsonResponse + { + try { + $eggId = $request->input('egg_id'); + $nestId = $request->input('nest_id'); + + $previewData = $this->eggChangeService->previewEggChange($server, $eggId, $nestId); + + // Log the preview action + Activity::event('server:settings.egg-preview') + ->property([ + 'current_egg_id' => $server->egg_id, + 'preview_egg_id' => $eggId, + 'preview_nest_id' => $nestId, + ]) + ->log(); + + return new JsonResponse($previewData); + } catch (Exception $e) { + Log::error('Failed to preview egg change', [ + 'server_id' => $server->id, + 'egg_id' => $request->input('egg_id'), + 'nest_id' => $request->input('nest_id'), + 'error' => $e->getMessage(), + ]); + + throw $e; + } + } + + /** + * Apply egg configuration changes asynchronously. + * This dispatches a background job to handle the complete egg change process. + * + * @throws \Throwable + */ + public function applyEggChange(ApplyEggChangeRequest $request, Server $server): JsonResponse + { + try { + $eggId = $request->input('egg_id'); + $nestId = $request->input('nest_id'); + $dockerImage = $request->input('docker_image'); + $startupCommand = $request->input('startup_command'); + $environment = $request->input('environment', []); + $shouldBackup = $request->input('should_backup', false); + $shouldWipe = $request->input('should_wipe', false); + + $result = $this->eggChangeService->applyEggChangeAsync( + $server, + $request->user(), + $eggId, + $nestId, + $dockerImage, + $startupCommand, + $environment, + $shouldBackup, + $shouldWipe + ); + + Activity::event('server:software.change-queued') + ->property([ + 'operation_id' => $result['operation_id'], + 'from_egg' => $server->egg_id, + 'to_egg' => $eggId, + 'should_backup' => $shouldBackup, + 'should_wipe' => $shouldWipe, + ]) + ->log(); + + return new JsonResponse($result, Response::HTTP_ACCEPTED); + } catch (Exception $e) { + Log::error('Failed to apply egg change', [ + 'server_id' => $server->id, + 'error' => $e->getMessage(), + ]); + + throw $e; + } + } + + public function getOperationStatus(Server $server, string $operationId): JsonResponse + { + $operation = $this->operationService->getOperation($server, $operationId); + return new JsonResponse($this->operationService->formatOperationResponse($operation)); + } + + public function getServerOperations(Server $server): JsonResponse + { + $operations = $this->operationService->getServerOperations($server); + return new JsonResponse(['operations' => $operations]); + } + + } + diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index b987c04f9..dfee51a52 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -5,12 +5,14 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Servers\StartupCommandService; +use Pterodactyl\Services\Servers\StartupCommandUpdateService; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; use Pterodactyl\Transformers\Api\Client\EggVariableTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Client\Servers\Startup\GetStartupRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Startup\UpdateStartupVariableRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Startup\UpdateStartupCommandRequest; class StartupController extends ClientApiController { @@ -19,6 +21,7 @@ class StartupController extends ClientApiController */ public function __construct( private StartupCommandService $startupCommandService, + private StartupCommandUpdateService $startupCommandUpdateService, private ServerVariableRepository $repository, ) { parent::__construct(); @@ -96,4 +99,59 @@ class StartupController extends ClientApiController ]) ->toArray(); } + + /** + * Updates the startup command for a server. + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Throwable + */ + public function updateCommand(UpdateStartupCommandRequest $request, Server $server): array + { + $this->startupCommandUpdateService->handle($server, $request->input('startup')); + + $startup = $this->startupCommandService->handle($server); + + return $this->fractal->collection( + $server->variables()->where('user_viewable', true)->get() + ) + ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->addMeta([ + 'startup_command' => $startup, + 'docker_images' => $server->egg->docker_images, + 'raw_startup_command' => $server->startup, + ]) + ->toArray(); + } + + /** + * Returns the default startup command for the server's egg. + */ + public function getDefaultCommand(GetStartupRequest $request, Server $server): array + { + return [ + 'default_startup_command' => $server->egg->startup, + ]; + } + + /** + * Process a startup command with variables for live preview. + */ + public function processCommand(GetStartupRequest $request, Server $server): array + { + $command = $request->input('command', $server->startup); + + // Temporarily update the server's startup command for processing + $originalStartup = $server->startup; + $server->startup = $command; + + $processedCommand = $this->startupCommandService->handle($server, false); + + // Restore original startup command + $server->startup = $originalStartup; + + return [ + 'processed_command' => $processedCommand, + ]; + } } diff --git a/app/Http/Controllers/Api/Client/Servers/SubdomainController.php b/app/Http/Controllers/Api/Client/Servers/SubdomainController.php new file mode 100644 index 000000000..18406e465 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/SubdomainController.php @@ -0,0 +1,207 @@ +attributes->get('server'); + + $this->authorize(Permission::ACTION_ALLOCATION_READ, $server); + + try { + // Check if server supports subdomains + $feature = $this->subdomainService->getServerSubdomainFeature($server); + if (!$feature) { + return response()->json([ + 'supported' => false, + 'message' => 'This server does not support subdomains.' + ]); + } + + $currentSubdomain = $server->activeSubdomain; + $availableDomains = $this->subdomainService->getAvailableDomains(); + + return response()->json([ + 'supported' => true, + 'current_subdomain' => $currentSubdomain ? [ + 'object' => 'server_subdomain', + 'attributes' => [ + 'subdomain' => $currentSubdomain->subdomain, + 'domain' => $currentSubdomain->domain->name, + 'domain_id' => $currentSubdomain->domain_id, + 'full_domain' => $currentSubdomain->full_domain, + 'is_active' => $currentSubdomain->is_active, + ] + ] : null, + 'available_domains' => $availableDomains, + ]); + } catch (\Exception $e) { + return response()->json([ + 'error' => 'Unable to retrieve subdomain information.' + ], 500); + } + } + + /** + * Create a new subdomain for the server, or replace an existing one. + * + * @return JsonResponse + */ + public function store(CreateSubdomainRequest $request): JsonResponse + { + $server = $request->attributes->get('server'); + + $this->authorize(Permission::ACTION_ALLOCATION_CREATE, $server); + + $data = $request->validated(); + + try { + // Get ALL active subdomains for this server (more than one should be impossible, but PHP makes me angry) + $existingSubdomains = $server->subdomains()->where('is_active', true)->get(); + + $domain = Domain::where('id', $data['domain_id']) + ->where('is_active', true) + ->first(); + + if (!$domain) { + return response()->json([ + 'error' => 'Selected domain is not available.' + ], 422); + } + + if ($existingSubdomains->isNotEmpty()) { + foreach ($existingSubdomains as $existingSubdomain) { + try { + $this->subdomainService->deleteSubdomain($existingSubdomain); + Log::info("Deleted existing subdomain {$existingSubdomain->full_domain} during replacement for server {$server->id}"); + } catch (\Exception $e) { + Log::error("Failed to delete existing subdomain {$existingSubdomain->full_domain} during replacement: {$e->getMessage()}"); + return response()->json([ + 'error' => 'Failed to remove existing subdomain. Please try again.' + ], 422); + } + } + // Refresh server relationship to ensure we get updated data + $server->refresh(); + } + + $serverSubdomain = $this->subdomainService->createSubdomain( + $server, + $domain, + $data['subdomain'] + ); + + return response()->json([ + 'message' => $existingSubdomains->isNotEmpty() ? 'Subdomain replaced successfully.' : 'Subdomain created successfully.', + 'subdomain' => [ + 'object' => 'server_subdomain', + 'attributes' => [ + 'subdomain' => $serverSubdomain->subdomain, + 'domain' => $serverSubdomain->domain->name, + 'domain_id' => $serverSubdomain->domain_id, + 'full_domain' => $serverSubdomain->full_domain, + 'is_active' => $serverSubdomain->is_active, + ], + ] + ], 201); + } catch (\Exception $e) { + return response()->json([ + 'error' => $existingSubdomains->isNotEmpty() ? 'Failed to replace subdomain.' : 'Failed to create subdomain.' + ], 422); + } + } + + /** + * Delete the server's subdomain. + */ + public function destroy(Request $request): JsonResponse + { + $server = $request->attributes->get('server'); + + $this->authorize(Permission::ACTION_ALLOCATION_DELETE, $server); + + try { + $serverSubdomains = $server->subdomains()->where('is_active', true)->get(); + if ($serverSubdomains->isEmpty()) { + return response()->json([ + 'error' => 'Server does not have any active subdomains.' + ], 404); + } + + foreach ($serverSubdomains as $serverSubdomain) { + $this->subdomainService->deleteSubdomain($serverSubdomain); + Log::info("Deleted subdomain {$serverSubdomain->full_domain} for server {$server->id}"); + } + + return response()->json([ + 'message' => 'Subdomain(s) deleted successfully.' + ]); + } catch (\Exception $e) { + return response()->json([ + 'error' => 'Failed to delete subdomain(s).' + ], 422); + } + } + + /** + * Check if a subdomain is available. + */ + public function checkAvailability(Request $request): JsonResponse + { + $server = $request->attributes->get('server'); + + $this->authorize(Permission::ACTION_ALLOCATION_READ, $server); + + $request->validate([ + 'subdomain' => 'required|string|min:1|max:63|regex:/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/', + 'domain_id' => 'required|integer|exists:domains,id', + ]); + + try { + $domain = Domain::where('id', $request->input('domain_id')) + ->where('is_active', true) + ->first(); + + if (!$domain) { + return response()->json([ + 'error' => 'Selected domain is not available.' + ], 422); + } + + $subdomain = strtolower(trim($request->input('subdomain'))); + $availabilityResult = $this->subdomainService->checkSubdomainAvailability($subdomain, $domain); + + return response()->json([ + 'available' => $availabilityResult['available'], + 'message' => $availabilityResult['message'] + ]); + } catch (\Exception $e) { + return response()->json([ + 'error' => 'Unable to check subdomain availability.' + ], 422); + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php index 9a3a1f509..ff49c78ff 100644 --- a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -61,7 +61,7 @@ class WebsocketController extends ClientApiController ]) ->handle($node, $user->id . $server->uuid); - $socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $node->getConnectionAddress()); + $socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $node->getBrowserConnectionAddress()); return new JsonResponse([ 'data' => [ diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php index aa01e6b3c..c567e5f14 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php @@ -14,124 +14,124 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class BackupRemoteUploadController extends Controller { - public const DEFAULT_MAX_PART_SIZE = 5 * 1024 * 1024 * 1024; + public const DEFAULT_MAX_PART_SIZE = 5 * 1024 * 1024 * 1024; - /** - * BackupRemoteUploadController constructor. - */ - public function __construct(private BackupManager $backupManager) - { + /** + * BackupRemoteUploadController constructor. + */ + public function __construct(private BackupManager $backupManager) + { + } + + /** + * Returns the required presigned urls to upload a backup to S3 cloud storage. + * + * @throws \Exception + * @throws \Throwable + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function __invoke(Request $request, string $backup): JsonResponse + { + // Get the node associated with the request. + /** @var \Pterodactyl\Models\Node $node */ + $node = $request->attributes->get('node'); + + // Get the size query parameter. + $size = (int) $request->query('size'); + if (empty($size)) { + throw new BadRequestHttpException('A non-empty "size" query parameter must be provided.'); } - /** - * Returns the required presigned urls to upload a backup to S3 cloud storage. - * - * @throws \Exception - * @throws \Throwable - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException - */ - public function __invoke(Request $request, string $backup): JsonResponse - { - // Get the node associated with the request. - /** @var \Pterodactyl\Models\Node $node */ - $node = $request->attributes->get('node'); + /** @var Backup $model */ + $model = Backup::query() + ->where('uuid', $backup) + ->firstOrFail(); - // Get the size query parameter. - $size = (int) $request->query('size'); - if (empty($size)) { - throw new BadRequestHttpException('A non-empty "size" query parameter must be provided.'); - } - - /** @var Backup $model */ - $model = Backup::query() - ->where('uuid', $backup) - ->firstOrFail(); - - // Check that the backup is "owned" by the node making the request. This avoids other nodes - // from messing with backups that they don't own. - /** @var \Pterodactyl\Models\Server $server */ - $server = $model->server; - if ($server->node_id !== $node->id) { - throw new HttpForbiddenException('You do not have permission to access that backup.'); - } - - // Prevent backups that have already been completed from trying to - // be uploaded again. - if (!is_null($model->completed_at)) { - throw new ConflictHttpException('This backup is already in a completed state.'); - } - - // Ensure we are using the S3 adapter. - $adapter = $this->backupManager->adapter(); - if (!$adapter instanceof S3Filesystem) { - throw new BadRequestHttpException('The configured backup adapter is not an S3 compatible adapter.'); - } - - // The path where backup will be uploaded to - $path = sprintf('%s/%s.tar.gz', $model->server->uuid, $model->uuid); - - // Get the S3 client - $client = $adapter->getClient(); - $expires = CarbonImmutable::now()->addMinutes(config('backups.presigned_url_lifespan', 60)); - - // Params for generating the presigned urls - $params = [ - 'Bucket' => $adapter->getBucket(), - 'Key' => $path, - 'ContentType' => 'application/x-gzip', - ]; - - $storageClass = config('backups.disks.s3.storage_class'); - if (!is_null($storageClass)) { - $params['StorageClass'] = $storageClass; - } - - // Execute the CreateMultipartUpload request - $result = $client->execute($client->getCommand('CreateMultipartUpload', $params)); - - // Get the UploadId from the CreateMultipartUpload request, this is needed to create - // the other presigned urls. - $params['UploadId'] = $result->get('UploadId'); - - // Retrieve configured part size - $maxPartSize = $this->getConfiguredMaxPartSize(); - - // Create as many UploadPart presigned urls as needed - $parts = []; - for ($i = 0; $i < ($size / $maxPartSize); ++$i) { - $parts[] = $client->createPresignedRequest( - $client->getCommand('UploadPart', array_merge($params, ['PartNumber' => $i + 1])), - $expires - )->getUri()->__toString(); - } - - // Set the upload_id on the backup in the database. - $model->update(['upload_id' => $params['UploadId']]); - - return new JsonResponse([ - 'parts' => $parts, - 'part_size' => $maxPartSize, - ]); + // Check that the backup is "owned" by the node making the request. This avoids other nodes + // from messing with backups that they don't own. + /** @var \Pterodactyl\Models\Server $server */ + $server = $model->server; + if ($server->node_id !== $node->id) { + throw new HttpForbiddenException('You do not have permission to access that backup.'); } - /** - * Get the configured maximum size of a single part in the multipart upload. - * - * The function tries to retrieve a configured value from the configuration. - * If no value is specified, a fallback value will be used. - * - * Note if the received config cannot be converted to int (0), is zero or is negative, - * the fallback value will be used too. - * - * The fallback value is {@see BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE}. - */ - private function getConfiguredMaxPartSize(): int - { - $maxPartSize = (int) config('backups.max_part_size', self::DEFAULT_MAX_PART_SIZE); - if ($maxPartSize <= 0) { - $maxPartSize = self::DEFAULT_MAX_PART_SIZE; - } - - return $maxPartSize; + // Prevent backups that have already been completed from trying to + // be uploaded again. + if (!is_null($model->completed_at)) { + throw new ConflictHttpException('This backup is already in a completed state.'); } + + // Ensure we are using the S3 adapter. + $adapter = $this->backupManager->adapter(); + if (!$adapter instanceof S3Filesystem) { + throw new BadRequestHttpException('The configured backup adapter is not an S3 compatible adapter.'); + } + + // The path where backup will be uploaded to + $path = sprintf('%s/%s.tar.gz', $model->server->uuid, $model->uuid); + + // Get the S3 client + $client = $adapter->getClient(); + $expires = CarbonImmutable::now()->addMinutes((int) config('backups.presigned_url_lifespan', 60)); + + // Params for generating the presigned urls + $params = [ + 'Bucket' => $adapter->getBucket(), + 'Key' => $path, + 'ContentType' => 'application/x-gzip', + ]; + + $storageClass = config('backups.disks.s3.storage_class'); + if (!is_null($storageClass)) { + $params['StorageClass'] = $storageClass; + } + + // Execute the CreateMultipartUpload request + $result = $client->execute($client->getCommand('CreateMultipartUpload', $params)); + + // Get the UploadId from the CreateMultipartUpload request, this is needed to create + // the other presigned urls. + $params['UploadId'] = $result->get('UploadId'); + + // Retrieve configured part size + $maxPartSize = $this->getConfiguredMaxPartSize(); + + // Create as many UploadPart presigned urls as needed + $parts = []; + for ($i = 0; $i < ($size / $maxPartSize); ++$i) { + $parts[] = $client->createPresignedRequest( + $client->getCommand('UploadPart', array_merge($params, ['PartNumber' => $i + 1])), + $expires + )->getUri()->__toString(); + } + + // Set the upload_id on the backup in the database. + $model->update(['upload_id' => $params['UploadId']]); + + return new JsonResponse([ + 'parts' => $parts, + 'part_size' => $maxPartSize, + ]); + } + + /** + * Get the configured maximum size of a single part in the multipart upload. + * + * The function tries to retrieve a configured value from the configuration. + * If no value is specified, a fallback value will be used. + * + * Note if the received config cannot be converted to int (0), is zero or is negative, + * the fallback value will be used too. + * + * The fallback value is {@see BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE}. + */ + private function getConfiguredMaxPartSize(): int + { + $maxPartSize = (int) config('backups.max_part_size', self::DEFAULT_MAX_PART_SIZE); + if ($maxPartSize <= 0) { + $maxPartSize = self::DEFAULT_MAX_PART_SIZE; + } + + return $maxPartSize; + } } diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php index 60eefd1e1..2492800ed 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -134,9 +134,21 @@ class BackupStatusController extends Controller ]; $client = $adapter->getClient(); + if (!$successful) { - $client->execute($client->getCommand('AbortMultipartUpload', $params)); - + try { + $client->execute($client->getCommand('AbortMultipartUpload', $params)); + \Log::info('Aborted multipart upload for failed backup', [ + 'backup_uuid' => $backup->uuid, + 'upload_id' => $backup->upload_id, + ]); + } catch (\Exception $e) { + \Log::warning('Failed to abort multipart upload', [ + 'backup_uuid' => $backup->uuid, + 'upload_id' => $backup->upload_id, + 'error' => $e->getMessage(), + ]); + } return; } @@ -145,17 +157,56 @@ class BackupStatusController extends Controller 'Parts' => [], ]; - if (is_null($parts)) { - $params['MultipartUpload']['Parts'] = $client->execute($client->getCommand('ListParts', $params))['Parts']; - } else { - foreach ($parts as $part) { - $params['MultipartUpload']['Parts'][] = [ - 'ETag' => $part['etag'], - 'PartNumber' => $part['part_number'], - ]; + try { + if (is_null($parts)) { + $listPartsResult = $client->execute($client->getCommand('ListParts', $params)); + $params['MultipartUpload']['Parts'] = $listPartsResult['Parts'] ?? []; + } else { + foreach ($parts as $part) { + // Validate part data + if (!isset($part['etag']) || !isset($part['part_number'])) { + throw new DisplayException('Invalid part data provided for multipart upload completion.'); + } + + $params['MultipartUpload']['Parts'][] = [ + 'ETag' => $part['etag'], + 'PartNumber' => (int) $part['part_number'], + ]; + } } - } - $client->execute($client->getCommand('CompleteMultipartUpload', $params)); + // Ensure we have parts to complete + if (empty($params['MultipartUpload']['Parts'])) { + throw new DisplayException('No parts found for multipart upload completion.'); + } + + $client->execute($client->getCommand('CompleteMultipartUpload', $params)); + + \Log::info('Successfully completed multipart upload', [ + 'backup_uuid' => $backup->uuid, + 'upload_id' => $backup->upload_id, + 'parts_count' => count($params['MultipartUpload']['Parts']), + ]); + + } catch (\Exception $e) { + \Log::error('Failed to complete multipart upload', [ + 'backup_uuid' => $backup->uuid, + 'upload_id' => $backup->upload_id, + 'error' => $e->getMessage(), + ]); + + // Try to abort the upload to clean up + try { + $client->execute($client->getCommand('AbortMultipartUpload', $params)); + } catch (\Exception $abortException) { + \Log::warning('Failed to abort multipart upload after completion failure', [ + 'backup_uuid' => $backup->uuid, + 'upload_id' => $backup->upload_id, + 'abort_error' => $abortException->getMessage(), + ]); + } + + throw $e; + } } } diff --git a/app/Http/Controllers/Base/LocaleController.php b/app/Http/Controllers/Base/LocaleController.php index 5214defdd..16a310092 100644 --- a/app/Http/Controllers/Base/LocaleController.php +++ b/app/Http/Controllers/Base/LocaleController.php @@ -2,11 +2,11 @@ namespace Pterodactyl\Http\Controllers\Base; -use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use Illuminate\Translation\Translator; use Illuminate\Contracts\Translation\Loader; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Http\Requests\Base\LocaleRequest; class LocaleController extends Controller { @@ -20,20 +20,11 @@ class LocaleController extends Controller /** * Returns translation data given a specific locale and namespace. */ - public function __invoke(Request $request): JsonResponse + public function __invoke(LocaleRequest $request): JsonResponse { - $locales = explode(' ', $request->input('locale') ?? ''); - $namespaces = explode(' ', $request->input('namespace') ?? ''); - - $response = []; - foreach ($locales as $locale) { - $response[$locale] = []; - foreach ($namespaces as $namespace) { - $response[$locale][$namespace] = $this->i18n( - $this->loader->load($locale, str_replace('.', '/', $namespace)) - ); - } - } + $locale = $request->input('locale'); + $namespace = $request->input('namespace'); + $response[$locale][$namespace] = $this->i18n($this->loader->load($locale, $namespace)); return new JsonResponse($response, 200, [ // Cache this in the browser for an hour, and allow the browser to use a stale diff --git a/app/Http/Controllers/Base/SystemStatusController.php b/app/Http/Controllers/Base/SystemStatusController.php new file mode 100644 index 000000000..376f3a7de --- /dev/null +++ b/app/Http/Controllers/Base/SystemStatusController.php @@ -0,0 +1,152 @@ + 'running', + 'timestamp' => now()->toIso8601String(), + 'metrics' => [ + 'uptime' => $this->getUptime(), + 'memory' => $this->getMemoryUsage(), + 'cpu' => $this->getCpuUsage(), + 'disk' => $this->getDiskUsage(), + ], + 'system' => [ + 'php_version' => PHP_VERSION, + 'os' => php_uname(), + 'hostname' => gethostname(), + 'load_average' => sys_getloadavg(), + ] + ]; + }); + + return response()->json($metrics); + + } catch (\Exception $e) { + return response()->json([ + 'status' => 'error', + 'message' => 'Failed to retrieve system metrics', + 'error' => $e->getMessage() + ], 500); + } + } + + private function getMemoryUsage(): array + { + if (PHP_OS_FAMILY === 'Darwin') { + $memory = shell_exec('vm_stat'); + if (!$memory) { + throw new \RuntimeException('Failed to execute vm_stat command'); + } + + // Parse memory stats more reliably + $stats = []; + foreach (explode("\n", $memory) as $line) { + if (preg_match('/Pages\s+([^:]+):\s+(\d+)/', $line, $matches)) { + $stats[strtolower($matches[1])] = (int) $matches[2]; + } + } + + $page_size = 4096; // Default page size for macOS + + $total_memory = $this->getTotalMemoryMac(); + $free_memory = ($stats['free'] ?? 0) * $page_size; + $used_memory = $total_memory - $free_memory; + + return [ + 'total' => $total_memory, + 'used' => $used_memory, + 'free' => $free_memory, + 'page_size' => $page_size + ]; + } + + // Linux memory calculation + $memory = shell_exec('free -b'); + if (!$memory) { + throw new \RuntimeException('Failed to execute free command'); + } + + if (!preg_match('/Mem:\s+(\d+)\s+(\d+)\s+(\d+)/', $memory, $matches)) { + throw new \RuntimeException('Failed to parse memory information'); + } + + return [ + 'total' => (int) $matches[1], + 'used' => (int) $matches[2], + 'free' => (int) $matches[3] + ]; + } + + private function getTotalMemoryMac(): int + { + $memory = shell_exec('sysctl hw.memsize'); + if (!$memory || !preg_match('/hw.memsize: (\d+)/', $memory, $matches)) { + throw new \RuntimeException('Failed to get total memory size'); + } + return (int) $matches[1]; + } + + private function getCpuUsage(): float + { + if (PHP_OS_FAMILY === 'Darwin') { + $cmd = "top -l 1 | grep -E '^CPU' | awk '{print $3}' | cut -d'%' -f1"; + } else { + $cmd = "top -bn1 | grep 'Cpu(s)' | awk '{print $2 + $4}'"; + } + + $usage = shell_exec($cmd); + if ($usage === null) { + throw new \RuntimeException('Failed to get CPU usage'); + } + + return (float) $usage; + } + + private function getDiskUsage(): array + { + $total = disk_total_space('/'); + $free = disk_free_space('/'); + + if ($total === false || $free === false) { + throw new \RuntimeException('Failed to get disk space information'); + } + + return [ + 'total' => $total, + 'free' => $free, + 'used' => $total - $free + ]; + } + + private function getUptime(): int + { + if (PHP_OS_FAMILY === 'Darwin') { + $uptime = shell_exec('sysctl -n kern.boottime'); + if (!$uptime || !preg_match('/sec = (\d+)/', $uptime, $matches)) { + throw new \RuntimeException('Failed to get system uptime'); + } + return time() - (int) $matches[1]; + } + + $uptime = @file_get_contents('/proc/uptime'); + if ($uptime === false) { + throw new \RuntimeException('Failed to read uptime file'); + } + + return (int) floatval($uptime); + } +} \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b7085d9ed..e81ac82d6 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -11,7 +11,6 @@ use Illuminate\Session\Middleware\StartSession; use Pterodactyl\Http\Middleware\EncryptCookies; use Pterodactyl\Http\Middleware\Api\IsValidJson; use Pterodactyl\Http\Middleware\VerifyCsrfToken; -use Pterodactyl\Http\Middleware\VerifyReCaptcha; use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\LanguageMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; @@ -36,66 +35,66 @@ use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; class Kernel extends HttpKernel { - /** - * The application's global HTTP middleware stack. - */ - protected $middleware = [ - TrustProxies::class, - HandleCors::class, - PreventRequestsDuringMaintenance::class, - ValidatePostSize::class, - TrimStrings::class, - ConvertEmptyStringsToNull::class, - ]; + /** + * The application's global HTTP middleware stack. + */ + protected $middleware = [ + TrustProxies::class, + HandleCors::class, + PreventRequestsDuringMaintenance::class, + ValidatePostSize::class, + TrimStrings::class, + ConvertEmptyStringsToNull::class, + ]; - /** - * The application's route middleware groups. - */ - protected $middlewareGroups = [ - 'web' => [ - EncryptCookies::class, - AddQueuedCookiesToResponse::class, - StartSession::class, - ShareErrorsFromSession::class, - VerifyCsrfToken::class, - SubstituteBindings::class, - LanguageMiddleware::class, - ], - 'api' => [ - EnsureStatefulRequests::class, - 'auth:sanctum', - IsValidJson::class, - TrackAPIKey::class, - RequireTwoFactorAuthentication::class, - AuthenticateIPAccess::class, - ], - 'application-api' => [ - SubstituteBindings::class, - AuthenticateApplicationUser::class, - ], - 'client-api' => [ - SubstituteClientBindings::class, - RequireClientApiKey::class, - ], - 'daemon' => [ - SubstituteBindings::class, - DaemonAuthenticate::class, - ], - ]; + /** + * The application's route middleware groups. + */ + protected $middlewareGroups = [ + 'web' => [ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + LanguageMiddleware::class, + ], + 'api' => [ + EnsureStatefulRequests::class, + 'auth:sanctum', + IsValidJson::class, + TrackAPIKey::class, + RequireTwoFactorAuthentication::class, + AuthenticateIPAccess::class, + ], + 'application-api' => [ + SubstituteBindings::class, + AuthenticateApplicationUser::class, + ], + 'client-api' => [ + SubstituteClientBindings::class, + RequireClientApiKey::class, + ], + 'daemon' => [ + SubstituteBindings::class, + DaemonAuthenticate::class, + ], + ]; - /** - * The application's route middleware. - */ - protected $middlewareAliases = [ - 'auth' => Authenticate::class, - 'auth.basic' => AuthenticateWithBasicAuth::class, - 'auth.session' => AuthenticateSession::class, - 'guest' => RedirectIfAuthenticated::class, - 'csrf' => VerifyCsrfToken::class, - 'throttle' => ThrottleRequests::class, - 'can' => Authorize::class, - 'bindings' => SubstituteBindings::class, - 'recaptcha' => VerifyReCaptcha::class, - 'node.maintenance' => MaintenanceMiddleware::class, - ]; + /** + * The application's route middleware. + */ + protected $middlewareAliases = [ + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'auth.session' => AuthenticateSession::class, + 'guest' => RedirectIfAuthenticated::class, + 'csrf' => VerifyCsrfToken::class, + 'throttle' => ThrottleRequests::class, + 'can' => Authorize::class, + 'bindings' => SubstituteBindings::class, + 'node.maintenance' => MaintenanceMiddleware::class, + 'captcha' => \Pterodactyl\Http\Middleware\VerifyCaptcha::class, + ]; } diff --git a/app/Http/Middleware/Api/Client/Server/ServerOperationRateLimit.php b/app/Http/Middleware/Api/Client/Server/ServerOperationRateLimit.php new file mode 100644 index 000000000..8df6249f2 --- /dev/null +++ b/app/Http/Middleware/Api/Client/Server/ServerOperationRateLimit.php @@ -0,0 +1,90 @@ +route('server'); + $user = $request->user(); + + $this->checkActiveOperations($server); + $this->logOperationAttempt($server, $user, $operationType); + + return $next($request); + } + + /** + * Check for active operations on the same server. + */ + private function checkActiveOperations(Server $server): void + { + try { + if (!$this->tableExists('server_operations')) { + return; + } + + $activeOperations = ServerOperation::forServer($server)->active()->count(); + + if ($activeOperations > 0) { + throw new TooManyRequestsHttpException( + 300, + 'Another operation is currently in progress for this server. Please wait for it to complete.' + ); + } + } catch (\Exception $e) { + Log::warning('Failed to check for active operations', [ + 'server_id' => $server->id, + 'error' => $e->getMessage(), + ]); + } + } + + /** + * Check if a database table exists. + */ + private function tableExists(string $tableName): bool + { + try { + return \Schema::hasTable($tableName); + } catch (\Exception $e) { + Log::warning('Failed to check if table exists', [ + 'table' => $tableName, + 'error' => $e->getMessage(), + ]); + return false; + } + } + + /** + * Log operation attempt for monitoring. + */ + private function logOperationAttempt(Server $server, $user, string $operationType): void + { + Log::info('Server operation attempt', [ + 'server_id' => $server->id, + 'server_uuid' => $server->uuid, + 'user_id' => $user->id, + 'operation_type' => $operationType, + 'timestamp' => now()->toISOString(), + ]); + } +} \ No newline at end of file diff --git a/app/Http/Middleware/VerifyCaptcha.php b/app/Http/Middleware/VerifyCaptcha.php new file mode 100644 index 000000000..9bb77c233 --- /dev/null +++ b/app/Http/Middleware/VerifyCaptcha.php @@ -0,0 +1,81 @@ +captcha = $captcha; + } + + /** + * Handle an incoming request. + */ + public function handle(Request $request, Closure $next) + { + // Skip verification if captcha is not enabled + $defaultDriver = $this->captcha->getDefaultDriver(); + + Log::info('VerifyCaptcha middleware triggered', [ + 'default_driver' => $defaultDriver, + 'request_url' => $request->url(), + 'request_method' => $request->method(), + ]); + + if ($defaultDriver === 'none') { + Log::info('Captcha verification skipped - driver is none'); + return $next($request); + } + + // Get the captcha response from the request + $driver = $this->captcha->driver(); + $fieldName = $driver->getResponseFieldName(); + $captchaResponse = $request->input($fieldName); + + Log::info('Captcha verification details', [ + 'driver_name' => $driver->getName(), + 'field_name' => $fieldName, + 'response_present' => !empty($captchaResponse), + 'response_length' => $captchaResponse ? strlen($captchaResponse) : 0, + 'all_request_keys' => array_keys($request->all()), + ]); + + if (empty($captchaResponse)) { + Log::warning('Captcha verification failed - no response provided', [ + 'field_name' => $fieldName, + 'request_data' => $request->all(), + ]); + throw new DisplayException('Please complete the captcha verification.'); + } + + // Verify the captcha response + $remoteIp = $request->ip(); + Log::info('Starting captcha verification', [ + 'remote_ip' => $remoteIp, + 'response_preview' => substr($captchaResponse, 0, 50) . '...', + ]); + + $verificationResult = $this->captcha->verify($captchaResponse, $remoteIp); + + Log::info('Captcha verification completed', [ + 'result' => $verificationResult, + ]); + + if (!$verificationResult) { + Log::warning('Captcha verification failed - verification returned false'); + throw new DisplayException('Captcha verification failed. Please try again.'); + } + + Log::info('Captcha verification successful - proceeding with request'); + return $next($request); + } +} diff --git a/app/Http/Middleware/VerifyReCaptcha.php b/app/Http/Middleware/VerifyReCaptcha.php deleted file mode 100644 index ef251c333..000000000 --- a/app/Http/Middleware/VerifyReCaptcha.php +++ /dev/null @@ -1,72 +0,0 @@ -config->get('recaptcha.enabled')) { - return $next($request); - } - - if ($request->filled('g-recaptcha-response')) { - $client = new Client(); - $res = $client->post($this->config->get('recaptcha.domain'), [ - 'form_params' => [ - 'secret' => $this->config->get('recaptcha.secret_key'), - 'response' => $request->input('g-recaptcha-response'), - ], - ]); - - if ($res->getStatusCode() === 200) { - $result = json_decode($res->getBody()); - - if ($result->success && (!$this->config->get('recaptcha.verify_domain') || $this->isResponseVerified($result, $request))) { - return $next($request); - } - } - } - - $this->dispatcher->dispatch( - new FailedCaptcha( - $request->ip(), - !empty($result) ? ($result->hostname ?? null) : null - ) - ); - - throw new HttpException(Response::HTTP_BAD_REQUEST, 'Failed to validate reCAPTCHA data.'); - } - - /** - * Determine if the response from the recaptcha servers was valid. - */ - private function isResponseVerified(\stdClass $result, Request $request): bool - { - if (!$this->config->get('recaptcha.verify_domain')) { - return false; - } - - $url = parse_url($request->url()); - - return $result->hostname === array_get($url, 'host'); - } -} diff --git a/app/Http/Requests/Admin/Node/NodeFormRequest.php b/app/Http/Requests/Admin/Node/NodeFormRequest.php index f95189f29..77d340199 100644 --- a/app/Http/Requests/Admin/Node/NodeFormRequest.php +++ b/app/Http/Requests/Admin/Node/NodeFormRequest.php @@ -14,11 +14,14 @@ class NodeFormRequest extends AdminFormRequest public function rules(): array { if ($this->method() === 'PATCH') { - return Node::getRulesForUpdate($this->route()->parameter('node')); + $rules = Node::getRulesForUpdate($this->route()->parameter('node')); + $rules['internal_fqdn'] = ['nullable', 'string', Fqdn::make('scheme')]; + return $rules; } $data = Node::getRules(); $data['fqdn'][] = Fqdn::make('scheme'); + $data['internal_fqdn'] = ['nullable', 'string', Fqdn::make('scheme')]; return $data; } diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php index 17608d9f2..b3d6f3bb9 100644 --- a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -6,45 +6,39 @@ use Pterodactyl\Http\Requests\Admin\AdminFormRequest; class AdvancedSettingsFormRequest extends AdminFormRequest { - /** - * Return all the rules to apply to this request's data. - */ - public function rules(): array - { - return [ - 'recaptcha:enabled' => 'required|in:true,false', - 'recaptcha:secret_key' => 'required|string|max:191', - 'recaptcha:website_key' => 'required|string|max:191', - 'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60', - 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', - 'pterodactyl:client_features:allocations:enabled' => 'required|in:true,false', - 'pterodactyl:client_features:allocations:range_start' => [ - 'nullable', - 'required_if:pterodactyl:client_features:allocations:enabled,true', - 'integer', - 'between:1024,65535', - ], - 'pterodactyl:client_features:allocations:range_end' => [ - 'nullable', - 'required_if:pterodactyl:client_features:allocations:enabled,true', - 'integer', - 'between:1024,65535', - 'gt:pterodactyl:client_features:allocations:range_start', - ], - ]; - } + /** + * Return all the rules to apply to this request's data. + */ + public function rules(): array + { + return [ + 'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60', + 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', + 'pterodactyl:client_features:allocations:enabled' => 'required|in:true,false', + 'pterodactyl:client_features:allocations:range_start' => [ + 'nullable', + 'required_if:pterodactyl:client_features:allocations:enabled,true', + 'integer', + 'between:1024,65535', + ], + 'pterodactyl:client_features:allocations:range_end' => [ + 'nullable', + 'required_if:pterodactyl:client_features:allocations:enabled,true', + 'integer', + 'between:1024,65535', + 'gt:pterodactyl:client_features:allocations:range_start', + ], + ]; + } - public function attributes(): array - { - return [ - 'recaptcha:enabled' => 'reCAPTCHA Enabled', - 'recaptcha:secret_key' => 'reCAPTCHA Secret Key', - 'recaptcha:website_key' => 'reCAPTCHA Website Key', - 'pterodactyl:guzzle:timeout' => 'HTTP Request Timeout', - 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', - 'pterodactyl:client_features:allocations:enabled' => 'Auto Create Allocations Enabled', - 'pterodactyl:client_features:allocations:range_start' => 'Starting Port', - 'pterodactyl:client_features:allocations:range_end' => 'Ending Port', - ]; - } + public function attributes(): array + { + return [ + 'pterodactyl:guzzle:timeout' => 'HTTP Request Timeout', + 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', + 'pterodactyl:client_features:allocations:enabled' => 'Auto Create Allocations Enabled', + 'pterodactyl:client_features:allocations:range_start' => 'Starting Port', + 'pterodactyl:client_features:allocations:range_end' => 'Ending Port', + ]; + } } diff --git a/app/Http/Requests/Admin/Settings/CaptchaSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/CaptchaSettingsFormRequest.php new file mode 100644 index 000000000..c5987f67a --- /dev/null +++ b/app/Http/Requests/Admin/Settings/CaptchaSettingsFormRequest.php @@ -0,0 +1,105 @@ + ['required', 'string', Rule::in(['none', 'turnstile', 'hcaptcha', 'recaptcha'])], + 'pterodactyl:captcha:turnstile:site_key' => [ + 'nullable', + 'string', + 'max:255', + 'required_if:pterodactyl:captcha:provider,turnstile', + ], + 'pterodactyl:captcha:turnstile:secret_key' => [ + 'nullable', + 'string', + 'max:255', + 'required_if:pterodactyl:captcha:provider,turnstile', + ], + 'pterodactyl:captcha:hcaptcha:site_key' => [ + 'nullable', + 'string', + 'max:255', + 'required_if:pterodactyl:captcha:provider,hcaptcha', + ], + 'pterodactyl:captcha:hcaptcha:secret_key' => [ + 'nullable', + 'string', + 'max:255', + 'required_if:pterodactyl:captcha:provider,hcaptcha', + ], + 'pterodactyl:captcha:recaptcha:site_key' => [ + 'nullable', + 'string', + 'max:255', + 'required_if:pterodactyl:captcha:provider,recaptcha', + ], + 'pterodactyl:captcha:recaptcha:secret_key' => [ + 'nullable', + 'string', + 'max:255', + 'required_if:pterodactyl:captcha:provider,recaptcha', + ], + ]; + } + + public function attributes(): array + { + return [ + 'pterodactyl:captcha:provider' => 'Captcha Provider', + 'pterodactyl:captcha:turnstile:site_key' => 'Turnstile Site Key', + 'pterodactyl:captcha:turnstile:secret_key' => 'Turnstile Secret Key', + 'pterodactyl:captcha:hcaptcha:site_key' => 'hCaptcha Site Key', + 'pterodactyl:captcha:hcaptcha:secret_key' => 'hCaptcha Secret Key', + 'pterodactyl:captcha:recaptcha:site_key' => 'reCAPTCHA Site Key', + 'pterodactyl:captcha:recaptcha:secret_key' => 'reCAPTCHA Secret Key', + ]; + } + + public function normalize(?array $only = null): array + { + $data = $this->validated(); + + // Clear provider-specific settings if provider is 'none' + if ($data['pterodactyl:captcha:provider'] === 'none') { + $data['pterodactyl:captcha:turnstile:site_key'] = ''; + $data['pterodactyl:captcha:turnstile:secret_key'] = ''; + $data['pterodactyl:captcha:hcaptcha:site_key'] = ''; + $data['pterodactyl:captcha:hcaptcha:secret_key'] = ''; + $data['pterodactyl:captcha:recaptcha:site_key'] = ''; + $data['pterodactyl:captcha:recaptcha:secret_key'] = ''; + } + + // Clear other provider settings when switching providers + if ($data['pterodactyl:captcha:provider'] === 'turnstile') { + $data['pterodactyl:captcha:hcaptcha:site_key'] = ''; + $data['pterodactyl:captcha:hcaptcha:secret_key'] = ''; + $data['pterodactyl:captcha:recaptcha:site_key'] = ''; + $data['pterodactyl:captcha:recaptcha:secret_key'] = ''; + } elseif ($data['pterodactyl:captcha:provider'] === 'hcaptcha') { + $data['pterodactyl:captcha:turnstile:site_key'] = ''; + $data['pterodactyl:captcha:turnstile:secret_key'] = ''; + $data['pterodactyl:captcha:recaptcha:site_key'] = ''; + $data['pterodactyl:captcha:recaptcha:secret_key'] = ''; + } elseif ($data['pterodactyl:captcha:provider'] === 'recaptcha') { + $data['pterodactyl:captcha:turnstile:site_key'] = ''; + $data['pterodactyl:captcha:turnstile:secret_key'] = ''; + $data['pterodactyl:captcha:hcaptcha:site_key'] = ''; + $data['pterodactyl:captcha:hcaptcha:secret_key'] = ''; + } + + // Apply the $only filter if provided, similar to parent class + if ($only !== null) { + return array_intersect_key($data, array_flip($only)); + } + + return $data; + } +} diff --git a/app/Http/Requests/Admin/Settings/DomainFormRequest.php b/app/Http/Requests/Admin/Settings/DomainFormRequest.php new file mode 100644 index 000000000..a982e8cd6 --- /dev/null +++ b/app/Http/Requests/Admin/Settings/DomainFormRequest.php @@ -0,0 +1,82 @@ +route('domain')?->id; + + return [ + 'name' => [ + 'required', + 'string', + 'max:191', + 'regex:/^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/', + $domainId ? "unique:domains,name,{$domainId}" : 'unique:domains,name', + ], + 'dns_provider' => 'required|string|in:cloudflare', + 'dns_config' => 'required|array', + 'dns_config.api_token' => 'required_if:dns_provider,cloudflare|string|min:1', + 'dns_config.zone_id' => 'sometimes|string|min:1', + 'is_active' => 'sometimes|boolean', + 'is_default' => 'sometimes|boolean', + ]; + } + + /** + * Get custom messages for validator errors. + */ + public function messages(): array + { + return [ + 'name.required' => 'A domain name is required.', + 'name.regex' => 'The domain name format is invalid.', + 'name.unique' => 'This domain is already configured.', + 'dns_provider.required' => 'A DNS provider must be selected.', + 'dns_provider.in' => 'The selected DNS provider is not supported.', + 'dns_config.required' => 'DNS configuration is required.', + 'dns_config.api_token.required_if' => 'API token is required for Cloudflare.', + ]; + } + + /** + * Get custom attributes for validator errors. + */ + public function attributes(): array + { + return [ + 'name' => 'domain name', + 'dns_provider' => 'DNS provider', + 'dns_config.api_token' => 'API token', + ]; + } + + /** + * Prepare the data for validation. + */ + protected function prepareForValidation(): void + { + // Normalize domain name to lowercase + if ($this->has('name')) { + $this->merge([ + 'name' => strtolower(trim($this->input('name'))), + ]); + } + + // Ensure boolean fields are properly cast + foreach (['is_active', 'is_default'] as $field) { + if ($this->has($field)) { + $this->merge([ + $field => filter_var($this->input($field), FILTER_VALIDATE_BOOLEAN), + ]); + } + } + } +} \ No newline at end of file diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index e80fdd4a7..3568c3858 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -22,6 +22,7 @@ class StoreNodeRequest extends ApplicationApiRequest 'name', 'location_id', 'fqdn', + 'internal_fqdn', 'scheme', 'behind_proxy', 'maintenance_mode', diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index a9d0ecbed..8d87eecec 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -33,10 +33,12 @@ class StoreServerRequest extends ApplicationApiRequest 'environment' => 'present|array', 'skip_scripts' => 'sometimes|boolean', 'oom_disabled' => 'sometimes|boolean', + 'exclude_from_resource_calculation' => 'sometimes|boolean', // Resource limitations 'limits' => 'required|array', 'limits.memory' => $rules['memory'], + 'limits.overhead_memory' => $rules['overhead_memory'], 'limits.swap' => $rules['swap'], 'limits.disk' => $rules['disk'], 'limits.io' => $rules['io'], @@ -82,6 +84,7 @@ class StoreServerRequest extends ApplicationApiRequest 'startup' => array_get($data, 'startup'), 'environment' => array_get($data, 'environment'), 'memory' => array_get($data, 'limits.memory'), + 'overhead_memory' => array_get($data, 'limits.overhead_memory', 0), 'swap' => array_get($data, 'limits.swap'), 'disk' => array_get($data, 'limits.disk'), 'io' => array_get($data, 'limits.io'), @@ -95,6 +98,7 @@ class StoreServerRequest extends ApplicationApiRequest 'allocation_limit' => array_get($data, 'feature_limits.allocations'), 'backup_limit' => array_get($data, 'feature_limits.backups'), 'oom_disabled' => array_get($data, 'oom_disabled'), + 'exclude_from_resource_calculation' => array_get($data, 'exclude_from_resource_calculation', false), ]; } diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index f1c977f11..64914e243 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -17,9 +17,11 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest return [ 'allocation' => $rules['allocation_id'], 'oom_disabled' => $rules['oom_disabled'], + 'exclude_from_resource_calculation' => $rules['exclude_from_resource_calculation'], 'limits' => 'sometimes|array', 'limits.memory' => $this->requiredToOptional('memory', $rules['memory'], true), + 'limits.overhead_memory' => $this->requiredToOptional('overhead_memory', $rules['overhead_memory'], true), 'limits.swap' => $this->requiredToOptional('swap', $rules['swap'], true), 'limits.io' => $this->requiredToOptional('io', $rules['io'], true), 'limits.cpu' => $this->requiredToOptional('cpu', $rules['cpu'], true), @@ -31,6 +33,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest // // @see https://github.com/pterodactyl/panel/issues/1500 'memory' => $this->requiredToOptional('memory', $rules['memory']), + 'overhead_memory' => $this->requiredToOptional('overhead_memory', $rules['overhead_memory']), 'swap' => $this->requiredToOptional('swap', $rules['swap']), 'io' => $this->requiredToOptional('io', $rules['io']), 'cpu' => $this->requiredToOptional('cpu', $rules['cpu']), diff --git a/app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php index d2d427f99..66ca528cf 100644 --- a/app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Backups/RestoreBackupRequest.php @@ -14,6 +14,6 @@ class RestoreBackupRequest extends ClientApiRequest public function rules(): array { - return ['truncate' => 'required|boolean']; + return []; } } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/ApplyEggChangeRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/ApplyEggChangeRequest.php new file mode 100644 index 000000000..2bf6de269 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Settings/ApplyEggChangeRequest.php @@ -0,0 +1,67 @@ + 'required|integer|exists:eggs,id', + 'nest_id' => 'required|integer|exists:nests,id', + 'docker_image' => 'sometimes|string|max:255', + 'startup_command' => 'sometimes|string|max:2048', + 'environment' => 'sometimes|array|max:50', + 'environment.*' => 'nullable|string|max:1024', + 'should_backup' => 'sometimes|boolean', + 'should_wipe' => 'sometimes|boolean', + ]; + } + + public function withValidator($validator): void + { + $validator->after(function ($validator) { + if ($this->filled(['egg_id', 'nest_id'])) { + $egg = Egg::where('id', $this->input('egg_id')) + ->where('nest_id', $this->input('nest_id')) + ->first(); + + if (!$egg) { + $validator->errors()->add('egg_id', 'The selected egg does not belong to the specified nest.'); + return; + } + + if ($this->filled('docker_image')) { + $dockerImages = array_values($egg->docker_images ?? []); + if (!empty($dockerImages) && !in_array($this->input('docker_image'), $dockerImages)) { + $validator->errors()->add('docker_image', 'The selected Docker image is not allowed for this egg.'); + } + } + + if ($this->filled('environment')) { + $eggVariables = $egg->variables()->pluck('env_variable')->toArray(); + foreach ($this->input('environment', []) as $key => $value) { + if (!in_array($key, $eggVariables)) { + $validator->errors()->add("environment.{$key}", 'This environment variable is not valid for the selected egg.'); + } + } + } + } + }); + } +} \ No newline at end of file diff --git a/app/Http/Requests/Api/Client/Servers/Settings/PreviewEggRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/PreviewEggRequest.php new file mode 100644 index 000000000..ea9c93f5e --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Settings/PreviewEggRequest.php @@ -0,0 +1,43 @@ + 'required|integer|exists:eggs,id', + 'nest_id' => 'required|integer|exists:nests,id', + ]; + } + + public function withValidator($validator): void + { + $validator->after(function ($validator) { + if ($this->filled(['egg_id', 'nest_id'])) { + $egg = Egg::where('id', $this->input('egg_id')) + ->where('nest_id', $this->input('nest_id')) + ->first(); + + if (!$egg) { + $validator->errors()->add('egg_id', 'The selected egg does not belong to the specified nest.'); + } + } + }); + } +} \ No newline at end of file diff --git a/app/Http/Requests/Api/Client/Servers/Settings/RevertDockerImageRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/RevertDockerImageRequest.php new file mode 100644 index 000000000..4d7f9631f --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Settings/RevertDockerImageRequest.php @@ -0,0 +1,59 @@ +route()->parameter('server'); + + Assert::isInstanceOf($server, Server::class); + + return [ + 'confirm' => 'required|boolean|accepted', + ]; + } + + public function messages(): array + { + return [ + 'confirm.required' => 'You must confirm that you understand this action cannot be undone without administrator assistance.', + 'confirm.accepted' => 'You must confirm that you understand this action cannot be undone without administrator assistance.', + ]; + } + + /** + * Check if the server has a custom docker image that can be reverted. + */ + public function authorize(): bool + { + if (!parent::authorize()) { + return false; + } + + /** @var Server $server */ + $server = $this->route()->parameter('server'); + + try { + // Check if the current image is not in the egg's allowed images + // This indicates it was set by an administrator as a custom image + return $server->hasCustomDockerImage(); + } catch (\RuntimeException $e) { + // If there's an issue with the egg configuration, deny access + return false; + } + } +} \ No newline at end of file diff --git a/app/Http/Requests/Api/Client/Servers/Settings/ServerOperationRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/ServerOperationRequest.php new file mode 100644 index 000000000..8fb365efe --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Settings/ServerOperationRequest.php @@ -0,0 +1,43 @@ +user()->can('settings.egg', $this->route()->parameter('server')); + } + + /** + * Get the validation rules that apply to the request. + */ + public function rules(): array + { + return [ + 'operation_id' => 'required|string|uuid', + ]; + } + + /** + * Get custom messages for validator errors. + */ + public function messages(): array + { + return [ + 'operation_id.required' => 'An operation ID is required.', + 'operation_id.uuid' => 'The operation ID must be a valid UUID.', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupCommandRequest.php b/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupCommandRequest.php new file mode 100644 index 000000000..4c54ee218 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupCommandRequest.php @@ -0,0 +1,28 @@ + 'required|string|min:1|max:10000', + ]; + } + + public function attributes(): array + { + return [ + 'startup' => 'startup command', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/Api/Client/Servers/Subdomain/CreateSubdomainRequest.php b/app/Http/Requests/Api/Client/Servers/Subdomain/CreateSubdomainRequest.php new file mode 100644 index 000000000..ad066a216 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subdomain/CreateSubdomainRequest.php @@ -0,0 +1,87 @@ + [ + 'required', + 'string', + 'min:1', + 'max:63', + 'regex:/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/', + function ($attribute, $value, $fail) { + if (preg_match('/[<>"\']/', $value)) { + $fail('Subdomain contains invalid characters.'); + return; + } + + $reserved = ['www', 'mail', 'ftp', 'api', 'admin', 'root', 'panel', + 'localhost', 'wildcard', 'ns1', 'ns2', 'dns', 'smtp', 'pop', + 'imap', 'webmail', 'cpanel', 'whm', 'autodiscover', 'autoconfig']; + if (in_array(strtolower($value), $reserved)) { + $fail('This subdomain is reserved and cannot be used.'); + return; + } + + $domainId = $this->input('domain_id'); + if ($domainId) { + $exists = ServerSubdomain::where('domain_id', $domainId) + ->where('subdomain', strtolower($value)) + ->where('is_active', true) + ->exists(); + + if ($exists) { + $fail('This subdomain is already taken.'); + } + } + }, + ], + 'domain_id' => [ + 'required', + 'integer', + 'min:1', + 'exists:domains,id', + function ($attribute, $value, $fail) { + $domain = Domain::where('id', $value) + ->where('is_active', true) + ->first(); + if (!$domain) { + $fail('The selected domain is not available.'); + } + }, + ], + ]; + } + + /** + * Prepare the data for validation. + */ + protected function prepareForValidation(): void + { + // Sanitize and normalize subdomain + if ($this->has('subdomain')) { + $subdomain = $this->input('subdomain'); + // Remove any potential harmful characters and normalize + $subdomain = preg_replace('/[^a-z0-9-]/', '', strtolower(trim($subdomain))); + // Remove multiple consecutive hyphens + $subdomain = preg_replace('/-+/', '-', $subdomain); + // Remove leading/trailing hyphens + $subdomain = trim($subdomain, '-'); + + $this->merge([ + 'subdomain' => $subdomain, + ]); + } + } +} \ No newline at end of file diff --git a/app/Http/Requests/Base/LocaleRequest.php b/app/Http/Requests/Base/LocaleRequest.php new file mode 100644 index 000000000..0a2cc03d7 --- /dev/null +++ b/app/Http/Requests/Base/LocaleRequest.php @@ -0,0 +1,16 @@ + ['required', 'string', 'regex:/^[a-z][a-z]$/'], + 'namespace' => ['required', 'string', 'regex:/^[a-z]{1,191}$/'], + ]; + } +} diff --git a/app/Http/Resources/ServerOperationResource.php b/app/Http/Resources/ServerOperationResource.php new file mode 100644 index 000000000..9b9906462 --- /dev/null +++ b/app/Http/Resources/ServerOperationResource.php @@ -0,0 +1,43 @@ +resource; + + return [ + 'operation_id' => $operation->operation_id, + 'type' => $operation->type, + 'status' => $operation->status, + 'message' => $operation->message, + 'created_at' => $operation->created_at->toISOString(), + 'updated_at' => $operation->updated_at->toISOString(), + 'started_at' => $operation->started_at?->toISOString(), + 'parameters' => $operation->parameters, + 'meta' => [ + 'is_active' => $operation->isActive(), + 'is_completed' => $operation->isCompleted(), + 'has_failed' => $operation->hasFailed(), + 'has_timed_out' => $operation->hasTimedOut(), + 'can_be_cancelled' => $operation->isActive() && !$operation->hasFailed(), + ], + ]; + } +} \ No newline at end of file diff --git a/app/Http/ViewComposers/AssetComposer.php b/app/Http/ViewComposers/AssetComposer.php index 2cd16c500..3fdc5ea2a 100644 --- a/app/Http/ViewComposers/AssetComposer.php +++ b/app/Http/ViewComposers/AssetComposer.php @@ -3,22 +3,60 @@ namespace Pterodactyl\Http\ViewComposers; use Illuminate\View\View; +use Illuminate\Support\Facades\Log; use Pterodactyl\Services\Helpers\AssetHashService; +use Pterodactyl\Services\Captcha\CaptchaManager; +use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface; class AssetComposer { - /** - * Provide access to the asset service in the views. - */ - public function compose(View $view): void - { - $view->with('siteConfiguration', [ - 'name' => config('app.name') ?? 'Pterodactyl', - 'locale' => config('app.locale') ?? 'en', - 'recaptcha' => [ - 'enabled' => config('recaptcha.enabled', false), - 'siteKey' => config('recaptcha.website_key') ?? '', - ], - ]); + protected CaptchaManager $captcha; + protected SettingsRepositoryInterface $settings; + + public function __construct(CaptchaManager $captcha, SettingsRepositoryInterface $settings) + { + $this->captcha = $captcha; + $this->settings = $settings; + } + + /** + * Provide access to the asset service in the views. + */ + public function compose(View $view): void + { + $view->with('siteConfiguration', [ + 'name' => config('app.name') ?? 'Pyrodactyl', + 'locale' => config('app.locale') ?? 'en', + 'timezone' => config('app.timezone') ?? '', + 'captcha' => [ + 'enabled' => $this->captcha->getDefaultDriver() !== 'none', + 'provider' => $this->captcha->getDefaultDriver(), + 'siteKey' => $this->getSiteKeyForCurrentProvider(), + 'scriptIncludes' => $this->captcha->getScriptIncludes(), + ], + ]); + } + + /** + * Get the site key for the currently active captcha provider. + */ + private function getSiteKeyForCurrentProvider(): string + { + $provider = $this->captcha->getDefaultDriver(); + + if ($provider === 'none') { + return ''; } + + try { + $driver = $this->captcha->driver(); + if (method_exists($driver, 'getSiteKey')) { + return $driver->getSiteKey(); + } + } catch (\Exception $e) { + // Silently fail to avoid exposing errors to frontend + } + + return ''; + } } diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 6aded8621..037de71d1 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -68,12 +68,20 @@ class RunTaskJob extends Job implements ShouldQueue $commandRepository->setServer($server)->send($this->task->payload); break; case Task::ACTION_BACKUP: + // Mark the task as running before initiating the backup to prevent duplicate runs + $this->task->update(['is_processing' => true]); $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); + $this->task->update(['is_processing' => false]); break; default: throw new \InvalidArgumentException('Invalid task action provided: ' . $this->task->action); } } catch (\Exception $exception) { + // Reset the processing flag if there was an error + if ($this->task->action === Task::ACTION_BACKUP) { + $this->task->update(['is_processing' => false]); + } + // If this isn't a DaemonConnectionException on a task that allows for failures // throw the exception back up the chain so that the task is stopped. if (!($this->task->continue_on_failure && $exception instanceof DaemonConnectionException)) { diff --git a/app/Jobs/Server/ApplyEggChangeJob.php b/app/Jobs/Server/ApplyEggChangeJob.php new file mode 100644 index 000000000..e688a23cf --- /dev/null +++ b/app/Jobs/Server/ApplyEggChangeJob.php @@ -0,0 +1,394 @@ +queue = 'standard'; + $this->timeout = config('server_operations.timeouts.egg_change', 1800); + } + + /** + * Execute the egg change job. + */ + public function handle( + InitiateBackupService $backupService, + ReinstallServerService $reinstallServerService, + StartupModificationService $startupModificationService, + DaemonFileRepository $fileRepository, + ServerOperationService $operationService, + SubdomainManagementService $subdomainService + ): void { + $operation = null; + + try { + $operation = ServerOperation::where('operation_id', $this->operationId)->firstOrFail(); + $operation->markAsStarted(); + + Activity::actor($this->user)->event('server:software.change-started') + ->property([ + 'operation_id' => $this->operationId, + 'from_egg' => $this->server->egg_id, + 'to_egg' => $this->eggId, + 'should_backup' => $this->shouldBackup, + 'should_wipe' => $this->shouldWipe, + ]) + ->log(); + + $egg = Egg::query() + ->with(['variables', 'nest']) + ->findOrFail($this->eggId); + + $backup = null; + + if ($this->shouldBackup) { + $backup = $this->createBackup($backupService, $operation); + } + + if ($this->shouldWipe) { + $this->wipeServerFiles($fileRepository, $operation, $backup); + } + + $this->applyServerChanges($egg, $startupModificationService, $reinstallServerService, $operation, $subdomainService); + + $this->logSuccessfulChange(); + + $operation->markAsCompleted('Software configuration applied successfully. Server installation completed.'); + + } catch (Exception $e) { + $this->handleJobFailure($e, $operation); + throw $e; + } + } + + /** + * Create backup before proceeding with changes. + */ + private function createBackup(InitiateBackupService $backupService, ServerOperation $operation): Backup + { + $operation->updateProgress('Creating backup before proceeding...'); + + // Get current and target egg names for better backup naming + $currentEgg = $this->server->egg; + $targetEgg = Egg::find($this->eggId); + + // Create descriptive backup name + $backupName = sprintf( + 'Pre-Change Backup: %s → %s (%s)', + $currentEgg->name ?? 'Unknown', + $targetEgg->name ?? 'Unknown', + now()->format('M j, Y g:i A') + ); + + // Limit backup name length to prevent database issues + if (strlen($backupName) > 190) { + $backupName = substr($backupName, 0, 187) . '...'; + } + + $backup = $backupService + ->setIsLocked(false) + ->handle($this->server, $backupName); + + Activity::actor($this->user)->event('server:backup.software-change') + ->property([ + 'backup_name' => $backupName, + 'backup_uuid' => $backup->uuid, + 'operation_id' => $this->operationId, + 'from_egg' => $this->server->egg_id, + 'to_egg' => $this->eggId, + ]) + ->log(); + + $operation->updateProgress('Waiting for backup to complete...'); + $this->waitForBackupCompletion($backup, $operation); + + $backup->refresh(); + if (!$backup->is_successful) { + throw new BackupFailedException('Backup failed. Aborting software change to prevent data loss.'); + } + + return $backup; + } + + /** + * Wipe server files if requested. + */ + private function wipeServerFiles(DaemonFileRepository $fileRepository, ServerOperation $operation, ?Backup $backup): void + { + $operation->updateProgress('Wiping server files...'); + + try { + $contents = $fileRepository->setServer($this->server)->getDirectory('/'); + + if (!empty($contents)) { + $filesToDelete = array_map(function($item) { + return $item['name']; + }, $contents); + + if (count($filesToDelete) > 1000) { + Log::warning('Large number of files to delete', [ + 'server_id' => $this->server->id, + 'file_count' => count($filesToDelete), + ]); + } + + $fileRepository->setServer($this->server)->deleteFiles('/', $filesToDelete); + + Activity::actor($this->user)->event('server:files.software-change-wipe') + ->property([ + 'operation_id' => $this->operationId, + 'from_egg' => $this->server->egg_id, + 'to_egg' => $this->eggId, + 'files_deleted' => count($filesToDelete), + 'backup_verified' => $backup ? true : false, + ]) + ->log(); + } + } catch (Exception $e) { + Log::error('Failed to wipe files', [ + 'server_id' => $this->server->id, + 'error' => $e->getMessage(), + ]); + + if (!$backup) { + throw new \RuntimeException('File wipe failed and no backup was created. Aborting operation to prevent data loss.'); + } + } + } + + /** + * Apply server configuration changes. + */ + private function applyServerChanges( + Egg $egg, + StartupModificationService $startupModificationService, + ReinstallServerService $reinstallServerService, + ServerOperation $operation, + SubdomainManagementService $subdomainService + ): void { + $operation->updateProgress('Applying software configuration...'); + + DB::transaction(function () use ($egg, $startupModificationService, $reinstallServerService, $operation, $subdomainService) { + // Check if we need to remove subdomain before changing egg + $activeSubdomain = $this->server->activeSubdomain; + if ($activeSubdomain) { + // Create a temporary server with the new egg to check compatibility + $tempServer = clone $this->server; + $tempServer->egg = $egg; + $tempServer->egg_id = $egg->id; + + // If new egg doesn't support subdomains, delete the existing subdomain + if (!$tempServer->supportsSubdomains()) { + $operation->updateProgress('Removing incompatible subdomain...'); + + try { + $subdomainService->deleteSubdomain($activeSubdomain); + + Activity::actor($this->user)->event('server:subdomain.deleted-egg-change') + ->property([ + 'operation_id' => $this->operationId, + 'subdomain' => $activeSubdomain->full_domain, + 'reason' => 'new_egg_incompatible', + 'from_egg' => $this->server->egg_id, + 'to_egg' => $this->eggId, + ]) + ->log(); + } catch (Exception $e) { + Log::warning('Failed to delete subdomain during egg change', [ + 'server_id' => $this->server->id, + 'subdomain' => $activeSubdomain->full_domain, + 'error' => $e->getMessage(), + ]); + + // Continue with egg change even if subdomain deletion fails + $operation->updateProgress('Warning: Could not fully remove subdomain, continuing with egg change...'); + } + } + } + + if ($this->server->egg_id !== $this->eggId || $this->server->nest_id !== $this->nestId) { + $this->server->update([ + 'egg_id' => $this->eggId, + 'nest_id' => $this->nestId, + ]); + } + + $updateData = [ + 'startup' => $this->startupCommand ?: $egg->startup, + 'docker_image' => $this->dockerImage, + 'environment' => $this->environment, + ]; + + $updatedServer = $startupModificationService + ->setUserLevel(User::USER_LEVEL_ADMIN) + ->handle($this->server, $updateData); + + $operation->updateProgress('Reinstalling server...'); + $reinstallServerService->handle($updatedServer); + + $operation->updateProgress('Finalizing installation...'); + }); + } + + /** + * Log successful software change. + */ + private function logSuccessfulChange(): void + { + Activity::actor($this->user)->event('server:software.changed') + ->property([ + 'operation_id' => $this->operationId, + 'original_egg_id' => $this->server->getOriginal('egg_id'), + 'new_egg_id' => $this->eggId, + 'original_nest_id' => $this->server->getOriginal('nest_id'), + 'new_nest_id' => $this->nestId, + 'original_image' => $this->server->getOriginal('image'), + 'new_image' => $this->dockerImage, + 'backup_created' => $this->shouldBackup, + 'files_wiped' => $this->shouldWipe, + ]) + ->log(); + } + + /** + * Handle job failure. + */ + public function failed(\Throwable $exception): void + { + try { + $operation = ServerOperation::where('operation_id', $this->operationId)->first(); + + Log::error('Egg change job failed', [ + 'server_id' => $this->server->id, + 'operation_id' => $this->operationId, + 'error' => $exception->getMessage(), + ]); + + if ($operation) { + $operation->markAsFailed('Job failed: ' . $exception->getMessage()); + } + + Activity::actor($this->user)->event('server:software.change-job-failed') + ->property([ + 'operation_id' => $this->operationId, + 'error' => $exception->getMessage(), + 'attempted_egg_id' => $this->eggId, + ]) + ->log(); + } catch (\Throwable $e) { + Log::critical('Failed to handle job failure properly', [ + 'operation_id' => $this->operationId, + 'original_error' => $exception->getMessage(), + 'handler_error' => $e->getMessage(), + ]); + } + } + + /** + * Wait for backup completion with timeout monitoring. + */ + private function waitForBackupCompletion(Backup $backup, ServerOperation $operation, int $timeoutMinutes = 30): void + { + $startTime = Carbon::now(); + $timeout = $startTime->addMinutes($timeoutMinutes); + $lastProgressUpdate = 0; + + while (Carbon::now()->lt($timeout)) { + $backup->refresh(); + + if ($backup->is_successful && !is_null($backup->completed_at)) { + $operation->updateProgress('Backup completed successfully'); + return; + } + + if (!is_null($backup->completed_at) && !$backup->is_successful) { + throw new BackupFailedException('Backup failed during creation process.'); + } + + $elapsed = Carbon::now()->diffInSeconds($startTime); + if ($elapsed - $lastProgressUpdate >= 30) { + $operation->updateProgress("Backup in progress..."); + $lastProgressUpdate = $elapsed; + } + + sleep(5); + } + + throw new BackupFailedException('Backup creation timed out after ' . $timeoutMinutes . ' minutes.'); + } + + /** + * Handle job failure with error logging. + */ + private function handleJobFailure(\Throwable $exception, ?ServerOperation $operation): void + { + Log::error('Egg change job failed', [ + 'operation_id' => $this->operationId, + 'error' => $exception->getMessage(), + 'server_id' => $this->server->id, + 'user_id' => $this->user->id, + ]); + + if ($operation) { + $operation->markAsFailed('Operation failed: ' . $exception->getMessage()); + } + + Activity::actor($this->user)->event('server:software.change-failed') + ->property([ + 'operation_id' => $this->operationId, + 'error' => $exception->getMessage(), + 'attempted_egg_id' => $this->eggId, + 'attempted_nest_id' => $this->nestId, + ]) + ->log(); + } +} \ No newline at end of file diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 4a6f1b65b..cb51bbbc7 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -62,153 +62,153 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; */ class ApiKey extends Model { - /** @use HasFactory<\Database\Factories\ApiKeyFactory> */ - use HasFactory; + /** @use HasFactory<\Database\Factories\ApiKeyFactory> */ + use HasFactory; - /** - * The resource name for this model when it is transformed into an - * API representation using fractal. - */ - public const RESOURCE_NAME = 'api_key'; - /** - * Different API keys that can exist on the system. - */ - public const TYPE_NONE = 0; - public const TYPE_ACCOUNT = 1; - /* @deprecated */ - public const TYPE_APPLICATION = 2; - /* @deprecated */ - public const TYPE_DAEMON_USER = 3; - /* @deprecated */ - public const TYPE_DAEMON_APPLICATION = 4; - /** - * The length of API key identifiers. - */ - public const IDENTIFIER_LENGTH = 16; - /** - * The length of the actual API key that is encrypted and stored - * in the database. - */ - public const KEY_LENGTH = 32; + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + public const RESOURCE_NAME = 'api_key'; + /** + * Different API keys that can exist on the system. + */ + public const TYPE_NONE = 0; + public const TYPE_ACCOUNT = 1; + /* @deprecated */ + public const TYPE_APPLICATION = 2; + /* @deprecated */ + public const TYPE_DAEMON_USER = 3; + /* @deprecated */ + public const TYPE_DAEMON_APPLICATION = 4; + /** + * The length of API key identifiers. + */ + public const IDENTIFIER_LENGTH = 16; + /** + * The length of the actual API key that is encrypted and stored + * in the database. + */ + public const KEY_LENGTH = 32; - /** - * The table associated with the model. - */ - protected $table = 'api_keys'; + /** + * The table associated with the model. + */ + protected $table = 'api_keys'; - /** - * Cast values to correct type. - */ - protected $casts = [ - 'allowed_ips' => 'array', - 'user_id' => 'int', - 'last_used_at' => 'datetime', - 'expires_at' => 'datetime', - self::CREATED_AT => 'datetime', - self::UPDATED_AT => 'datetime', - 'r_' . AdminAcl::RESOURCE_USERS => 'int', - 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int', - 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int', - 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'int', - 'r_' . AdminAcl::RESOURCE_EGGS => 'int', - 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'int', - 'r_' . AdminAcl::RESOURCE_NESTS => 'int', - 'r_' . AdminAcl::RESOURCE_NODES => 'int', - 'r_' . AdminAcl::RESOURCE_SERVERS => 'int', - ]; + /** + * Cast values to correct type. + */ + protected $casts = [ + 'allowed_ips' => 'array', + 'user_id' => 'int', + 'last_used_at' => 'datetime', + 'expires_at' => 'datetime', + self::CREATED_AT => 'datetime', + self::UPDATED_AT => 'datetime', + 'r_' . AdminAcl::RESOURCE_USERS => 'int', + 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int', + 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int', + 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'int', + 'r_' . AdminAcl::RESOURCE_EGGS => 'int', + 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'int', + 'r_' . AdminAcl::RESOURCE_NESTS => 'int', + 'r_' . AdminAcl::RESOURCE_NODES => 'int', + 'r_' . AdminAcl::RESOURCE_SERVERS => 'int', + ]; - /** - * Fields that are mass assignable. - */ - protected $fillable = [ - 'identifier', - 'token', - 'allowed_ips', - 'memo', - 'last_used_at', - 'expires_at', - ]; + /** + * Fields that are mass assignable. + */ + protected $fillable = [ + 'identifier', + 'token', + 'allowed_ips', + 'memo', + 'last_used_at', + 'expires_at', + ]; - /** - * Fields that should not be included when calling toArray() or toJson() - * on this model. - */ - protected $hidden = ['token']; + /** + * Fields that should not be included when calling toArray() or toJson() + * on this model. + */ + protected $hidden = ['token']; - /** - * Rules to protect against invalid data entry to DB. - */ - public static array $validationRules = [ - 'user_id' => 'required|exists:users,id', - 'key_type' => 'present|integer|min:0|max:4', - 'identifier' => 'required|string|size:16|unique:api_keys,identifier', - 'token' => 'required|string', - 'memo' => 'required|nullable|string|max:500', - 'allowed_ips' => 'nullable|array', - 'allowed_ips.*' => 'string', - 'last_used_at' => 'nullable|date', - 'expires_at' => 'nullable|date', - 'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_EGGS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_NESTS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3', - ]; + /** + * Rules to protect against invalid data entry to DB. + */ + public static array $validationRules = [ + 'user_id' => 'required|exists:users,id', + 'key_type' => 'present|integer|min:0|max:4', + 'identifier' => 'required|string|size:16|unique:api_keys,identifier', + 'token' => 'required|string', + 'memo' => 'required|nullable|string|max:500', + 'allowed_ips' => 'nullable|array', + 'allowed_ips.*' => 'string', + 'last_used_at' => 'nullable|date', + 'expires_at' => 'nullable|date', + 'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_EGGS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_NESTS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3', + ]; - /** - * Returns the user this token is assigned to. - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); + /** + * Returns the user this token is assigned to. + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Required for support with Laravel Sanctum. + * + * @see \Laravel\Sanctum\Guard::supportsTokens() + */ + public function tokenable(): BelongsTo + { + return $this->user(); + } + + /** + * Finds the model matching the provided token. + */ + public static function findToken(string $token): ?self + { + $identifier = substr($token, 0, self::IDENTIFIER_LENGTH); + + $model = static::where('identifier', $identifier)->first(); + if (!is_null($model) && decrypt($model->token) === substr($token, strlen($identifier))) { + return $model; } - /** - * Required for support with Laravel Sanctum. - * - * @see \Laravel\Sanctum\Guard::supportsTokens() - */ - public function tokenable(): BelongsTo - { - return $this->user(); - } + return null; + } - /** - * Finds the model matching the provided token. - */ - public static function findToken(string $token): ?self - { - $identifier = substr($token, 0, self::IDENTIFIER_LENGTH); + /** + * Returns the standard prefix for API keys in the system. + */ + public static function getPrefixForType(int $type): string + { + Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]); - $model = static::where('identifier', $identifier)->first(); - if (!is_null($model) && decrypt($model->token) === substr($token, strlen($identifier))) { - return $model; - } + return $type === self::TYPE_ACCOUNT ? 'pyro_' : 'pyro_'; + } - return null; - } + /** + * Generates a new identifier for an API key. + */ + public static function generateTokenIdentifier(int $type): string + { + $prefix = self::getPrefixForType($type); - /** - * Returns the standard prefix for API keys in the system. - */ - public static function getPrefixForType(int $type): string - { - Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]); - - return $type === self::TYPE_ACCOUNT ? 'ptlc_' : 'ptla_'; - } - - /** - * Generates a new identifier for an API key. - */ - public static function generateTokenIdentifier(int $type): string - { - $prefix = self::getPrefixForType($type); - - return $prefix . Str::random(self::IDENTIFIER_LENGTH - strlen($prefix)); - } + return $prefix . Str::random(self::IDENTIFIER_LENGTH - strlen($prefix)); + } } diff --git a/app/Models/Backup.php b/app/Models/Backup.php index 9b546ba28..26004e9bd 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; * @property bool $is_locked * @property string $name * @property string[] $ignored_files + * @property array|null $server_state * @property string $disk * @property string|null $checksum * @property int $bytes @@ -45,6 +46,7 @@ class Backup extends Model 'is_successful' => 'bool', 'is_locked' => 'bool', 'ignored_files' => 'array', + 'server_state' => 'array', 'bytes' => 'int', 'completed_at' => 'datetime', ]; @@ -66,6 +68,7 @@ class Backup extends Model 'is_locked' => 'boolean', 'name' => 'required|string', 'ignored_files' => 'array', + 'server_state' => 'nullable|array', 'disk' => 'required|string', 'checksum' => 'nullable|string', 'bytes' => 'numeric', diff --git a/app/Models/Domain.php b/app/Models/Domain.php new file mode 100644 index 000000000..90ece1323 --- /dev/null +++ b/app/Models/Domain.php @@ -0,0 +1,98 @@ + */ + use HasFactory; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + public const RESOURCE_NAME = 'domain'; + + /** + * The table associated with the model. + */ + protected $table = 'domains'; + + /** + * Fields that are mass assignable. + */ + protected $fillable = [ + 'name', + 'dns_provider', + 'dns_config', + 'is_active', + 'is_default', + ]; + + /** + * Cast values to correct type. + */ + protected $casts = [ + 'dns_config' => 'array', + 'is_active' => 'boolean', + 'is_default' => 'boolean', + self::CREATED_AT => 'datetime', + self::UPDATED_AT => 'datetime', + ]; + + public static array $validationRules = [ + 'name' => 'required|string|max:191|unique:domains', + 'dns_provider' => 'required|string|max:191', + 'dns_config' => 'required|array', + 'is_active' => 'sometimes|boolean', + 'is_default' => 'sometimes|boolean', + ]; + + /** + * Gets all server subdomains associated with this domain. + */ + public function serverSubdomains(): HasMany + { + return $this->hasMany(ServerSubdomain::class); + } + + /** + * Gets only active server subdomains associated with this domain. + */ + public function activeSubdomains(): HasMany + { + return $this->hasMany(ServerSubdomain::class)->where('is_active', true); + } + + /** + * Get the route key for the model. + */ + public function getRouteKeyName(): string + { + return 'id'; + } + + /** + * Get the default domain for automatic subdomain generation. + */ + public static function getDefault(): ?self + { + return static::where('is_active', true) + ->where('is_default', true) + ->first(); + } +} \ No newline at end of file diff --git a/app/Models/Node.php b/app/Models/Node.php index fdb1a43e9..18656c6e5 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -20,6 +20,8 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough; * @property string|null $description * @property int $location_id * @property string $fqdn + * @property string|null $internal_fqdn + * @property bool $use_separate_fqdns * @property string $scheme * @property bool $behind_proxy * @property bool $maintenance_mode @@ -77,18 +79,34 @@ class Node extends Model 'behind_proxy' => 'boolean', 'public' => 'boolean', 'maintenance_mode' => 'boolean', + 'use_separate_fqdns' => 'boolean', ]; /** * Fields that are mass assignable. */ protected $fillable = [ - 'public', 'name', 'location_id', - 'fqdn', 'scheme', 'behind_proxy', - 'memory', 'memory_overallocate', 'disk', - 'disk_overallocate', 'upload_size', 'daemonBase', - 'daemonSFTP', 'daemonListen', - 'description', 'maintenance_mode', + 'uuid', + 'public', + 'name', + 'location_id', + 'fqdn', + 'internal_fqdn', + 'use_separate_fqdns', + 'scheme', + 'behind_proxy', + 'memory', + 'memory_overallocate', + 'disk', + 'disk_overallocate', + 'upload_size', + 'daemonBase', + 'daemonSFTP', + 'daemonListen', + 'daemon_token_id', + 'daemon_token', + 'description', + 'maintenance_mode', ]; public static array $validationRules = [ @@ -97,6 +115,8 @@ class Node extends Model 'location_id' => 'required|exists:locations,id', 'public' => 'boolean', 'fqdn' => 'required|string', + 'internal_fqdn' => 'nullable|string', + 'use_separate_fqdns' => 'sometimes|boolean', 'scheme' => 'required', 'behind_proxy' => 'boolean', 'memory' => 'required|numeric|min:1', @@ -122,16 +142,42 @@ class Node extends Model 'daemonSFTP' => 2022, 'daemonListen' => 8080, 'maintenance_mode' => false, + 'use_separate_fqdns' => false, ]; /** * Get the connection address to use when making calls to this node. + * This will use the internal FQDN if separate FQDNs are enabled and internal_fqdn is set, + * otherwise it will fall back to the regular fqdn. */ public function getConnectionAddress(): string + { + $fqdn = $this->getInternalFqdn(); + return sprintf('%s://%s:%s', $this->scheme, $fqdn, $this->daemonListen); + } + + /** + * Get the browser connection address for WebSocket connections. + * This always uses the public fqdn field. + */ + public function getBrowserConnectionAddress(): string { return sprintf('%s://%s:%s', $this->scheme, $this->fqdn, $this->daemonListen); } + /** + * Get the appropriate FQDN for internal panel-to-Wings communication. + */ + public function getInternalFqdn(): string + { + // Use internal FQDN if it's provided and not empty + if (!empty($this->internal_fqdn)) { + return $this->internal_fqdn; + } + + return $this->fqdn; + } + /** * Returns the configuration as an array. */ @@ -147,8 +193,8 @@ class Node extends Model 'port' => $this->daemonListen, 'ssl' => [ 'enabled' => (!$this->behind_proxy && $this->scheme === 'https'), - 'cert' => '/etc/letsencrypt/live/' . Str::lower($this->fqdn) . '/fullchain.pem', - 'key' => '/etc/letsencrypt/live/' . Str::lower($this->fqdn) . '/privkey.pem', + 'cert' => '/etc/letsencrypt/live/' . Str::lower($this->getInternalFqdn()) . '/fullchain.pem', + 'key' => '/etc/letsencrypt/live/' . Str::lower($this->getInternalFqdn()) . '/privkey.pem', ], 'upload_limit' => $this->upload_size, ], @@ -231,6 +277,10 @@ class Node extends Model $memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100)); $diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100)); - return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit; + // Calculate used resources excluding servers marked for exclusion + $usedMemory = $this->servers()->where('exclude_from_resource_calculation', false)->sum('memory'); + $usedDisk = $this->servers()->where('exclude_from_resource_calculation', false)->sum('disk'); + + return ($usedMemory + $memory) <= $memoryLimit && ($usedDisk + $disk) <= $diskLimit; } } diff --git a/app/Models/Permission.php b/app/Models/Permission.php index c6af2869e..2abcce60e 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -58,6 +58,7 @@ class Permission extends Model public const ACTION_STARTUP_READ = 'startup.read'; public const ACTION_STARTUP_UPDATE = 'startup.update'; + public const ACTION_STARTUP_COMMAND = 'startup.command'; public const ACTION_STARTUP_DOCKER_IMAGE = 'startup.docker-image'; public const ACTION_STARTUP_SOFTWARE = 'startup.software'; @@ -172,6 +173,7 @@ class Permission extends Model 'keys' => [ 'read' => 'Allows a user to view the startup variables for a server.', 'update' => 'Allows a user to modify the startup variables for the server.', + 'command' => 'Allows a user to modify the startup command for the server.', 'docker-image' => 'Allows a user to modify the Docker image used when running the server.', 'software' => 'Allows a user to modify the game / software used for the server.', ], diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index 2d85314f0..5843da140 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -120,8 +120,8 @@ class Schedule extends Model { $formatted = sprintf('%s %s %s %s %s', $this->cron_minute, $this->cron_hour, $this->cron_day_of_month, $this->cron_month, $this->cron_day_of_week); - return CarbonImmutable::createFromTimestamp( - (new CronExpression($formatted))->getNextRunDate()->getTimestamp() + return CarbonImmutable::instance( + (new CronExpression($formatted))->getNextRunDate() ); } diff --git a/app/Models/Server.php b/app/Models/Server.php index 8c89b85d8..0a3642b75 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -27,12 +27,14 @@ use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; * @property bool $skip_scripts * @property int $owner_id * @property int $memory + * @property int $overhead_memory * @property int $swap * @property int $disk * @property int $io * @property int $cpu * @property string|null $threads * @property bool $oom_disabled + * @property bool $exclude_from_resource_calculation * @property int $allocation_id * @property int $nest_id * @property int $egg_id @@ -87,6 +89,7 @@ use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; * @method static \Illuminate\Database\Eloquent\Builder|Server whereImage($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereIo($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereMemory($value) + * @method static \Illuminate\Database\Eloquent\Builder|Server whereOverheadMemory($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereName($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereNestId($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereNodeId($value) @@ -134,6 +137,7 @@ class Server extends Model protected $attributes = [ 'status' => self::STATUS_INSTALLING, 'oom_disabled' => true, + 'exclude_from_resource_calculation' => false, 'installed_at' => null, ]; @@ -155,11 +159,13 @@ class Server extends Model 'description' => 'string', 'status' => 'nullable|string', 'memory' => 'required|numeric|min:0', + 'overhead_memory' => 'sometimes|numeric|min:0', 'swap' => 'required|numeric|min:-1', 'io' => 'required|numeric|between:10,1000', 'cpu' => 'required|numeric|min:0', 'threads' => 'nullable|regex:/^[0-9-,]+$/', 'oom_disabled' => 'sometimes|boolean', + 'exclude_from_resource_calculation' => 'sometimes|boolean', 'disk' => 'required|numeric|min:0', 'allocation_id' => 'required|bail|unique:servers|exists:allocations,id', 'nest_id' => 'required|exists:nests,id', @@ -180,11 +186,13 @@ class Server extends Model 'skip_scripts' => 'boolean', 'owner_id' => 'integer', 'memory' => 'integer', + 'overhead_memory' => 'integer', 'swap' => 'integer', 'disk' => 'integer', 'io' => 'integer', 'cpu' => 'integer', 'oom_disabled' => 'boolean', + 'exclude_from_resource_calculation' => 'boolean', 'allocation_id' => 'integer', 'nest_id' => 'integer', 'egg_id' => 'integer', @@ -217,6 +225,40 @@ class Server extends Model return $this->status === self::STATUS_SUSPENDED; } + /** + * Checks if the server has a custom docker image set by an administrator. + * A custom image is one that is not in the egg's allowed docker images. + */ + public function hasCustomDockerImage(): bool + { + // Ensure we have egg data and docker images + if (!$this->egg || !is_array($this->egg->docker_images) || empty($this->egg->docker_images)) { + return false; + } + + return !in_array($this->image, array_values($this->egg->docker_images)); + } + + /** + * Gets the default docker image from the egg specification. + */ + public function getDefaultDockerImage(): string + { + // Ensure we have egg data and docker images + if (!$this->egg || !is_array($this->egg->docker_images) || empty($this->egg->docker_images)) { + throw new \RuntimeException('Server egg has no docker images configured.'); + } + + $eggDockerImages = $this->egg->docker_images; + $defaultImage = reset($eggDockerImages); + + if (empty($defaultImage)) { + throw new \RuntimeException('Server egg has no valid default docker image.'); + } + + return $defaultImage; + } + /** * Gets the user who owns the server. */ @@ -346,6 +388,82 @@ class Server extends Model return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); } + /** + * Gets all subdomains associated with this server. + */ + public function subdomains(): HasMany + { + return $this->hasMany(ServerSubdomain::class); + } + + /** + * Gets the active subdomain for this server. + */ + public function activeSubdomain(): HasOne + { + return $this->hasOne(ServerSubdomain::class)->where('is_active', true); + } + + /** + * Check if this server supports subdomains based on its egg features. + */ + public function supportsSubdomains(): bool + { + if (!$this->egg) { + return false; + } + + // Check direct features + if (is_array($this->egg->features)) { + foreach ($this->egg->features as $feature) { + if (str_starts_with($feature, 'subdomain_')) { + return true; + } + } + } + + // Check inherited features + if (is_array($this->egg->inherit_features)) { + foreach ($this->egg->inherit_features as $feature) { + if (str_starts_with($feature, 'subdomain_')) { + return true; + } + } + } + + return false; + } + + /** + * Get the subdomain feature type for this server. + */ + public function getSubdomainFeature(): ?string + { + if (!$this->egg) { + return null; + } + + // Check direct features + if (is_array($this->egg->features)) { + foreach ($this->egg->features as $feature) { + if (str_starts_with($feature, 'subdomain_')) { + return $feature; + } + } + } + + // Check inherited features + if (is_array($this->egg->inherit_features)) { + foreach ($this->egg->inherit_features as $feature) { + if (str_starts_with($feature, 'subdomain_')) { + return $feature; + } + } + } + + return null; + } + /** * Checks if the server is currently in a user-accessible state. If not, an * exception is raised. This should be called whenever something needs to make diff --git a/app/Models/ServerOperation.php b/app/Models/ServerOperation.php new file mode 100644 index 000000000..c0089e761 --- /dev/null +++ b/app/Models/ServerOperation.php @@ -0,0 +1,165 @@ + 'array', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'started_at' => 'datetime', + ]; + + public function server(): BelongsTo + { + return $this->belongsTo(Server::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function isActive(): bool + { + return in_array($this->status, [self::STATUS_PENDING, self::STATUS_RUNNING]); + } + + public function isCompleted(): bool + { + return $this->status === self::STATUS_COMPLETED; + } + + public function hasFailed(): bool + { + return $this->status === self::STATUS_FAILED; + } + + public function scopeForServer($query, Server $server) + { + return $query->where('server_id', $server->id); + } + + public function scopeActive($query) + { + return $query->whereIn('status', [self::STATUS_PENDING, self::STATUS_RUNNING]); + } + + public function scopeOfType($query, string $type) + { + return $query->where('type', $type); + } + + public function scopeTimedOut($query, int $timeoutMinutes = 30) + { + return $query->where('status', self::STATUS_RUNNING) + ->whereNotNull('started_at') + ->where('started_at', '<', now()->subMinutes($timeoutMinutes)); + } + + public function scopeForCleanup($query, int $daysOld = 30) + { + return $query->whereIn('status', [self::STATUS_COMPLETED, self::STATUS_FAILED, self::STATUS_CANCELLED]) + ->where('created_at', '<', now()->subDays($daysOld)); + } + + /** + * Check if the operation has exceeded the timeout threshold. + */ + public function hasTimedOut(int $timeoutMinutes = 30): bool + { + if (!$this->isActive() || !$this->started_at) { + return false; + } + + return $this->started_at->diffInMinutes(now()) > $timeoutMinutes; + } + + /** + * Mark operation as started and update status. + */ + public function markAsStarted(): bool + { + return $this->update([ + 'status' => self::STATUS_RUNNING, + 'started_at' => now(), + 'message' => 'Operation started...', + ]); + } + + /** + * Mark operation as completed with optional message. + */ + public function markAsCompleted(string $message = 'Operation completed successfully'): bool + { + return $this->update([ + 'status' => self::STATUS_COMPLETED, + 'message' => $message, + ]); + } + + /** + * Mark operation as failed with error message. + */ + public function markAsFailed(string $message): bool + { + return $this->update([ + 'status' => self::STATUS_FAILED, + 'message' => $message, + ]); + } + + /** + * Update operation progress message. + */ + public function updateProgress(string $message): bool + { + return $this->update(['message' => $message]); + } + + /** + * Get operation duration in seconds if started. + */ + public function getDurationInSeconds(): ?int + { + if (!$this->started_at) { + return null; + } + + return $this->started_at->diffInSeconds(now()); + } +} \ No newline at end of file diff --git a/app/Models/ServerSubdomain.php b/app/Models/ServerSubdomain.php new file mode 100644 index 000000000..74d9c1b20 --- /dev/null +++ b/app/Models/ServerSubdomain.php @@ -0,0 +1,101 @@ + */ + use HasFactory; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + public const RESOURCE_NAME = 'server_subdomain'; + + /** + * The table associated with the model. + */ + protected $table = 'server_subdomains'; + + /** + * Fields that are mass assignable. + */ + protected $fillable = [ + 'server_id', + 'domain_id', + 'subdomain', + 'record_type', + 'dns_records', + 'is_active', + ]; + + /** + * Cast values to correct type. + */ + protected $casts = [ + 'server_id' => 'integer', + 'domain_id' => 'integer', + 'dns_records' => 'array', + 'is_active' => 'boolean', + self::CREATED_AT => 'datetime', + self::UPDATED_AT => 'datetime', + ]; + + public static array $validationRules = [ + 'server_id' => 'required|integer|exists:servers,id', + 'domain_id' => 'required|integer|exists:domains,id', + 'subdomain' => 'required|string|max:191', + 'record_type' => 'required|string|max:10', + 'dns_records' => 'sometimes|array', + 'is_active' => 'sometimes|boolean', + ]; + + /** + * Gets the server this subdomain belongs to. + */ + public function server(): BelongsTo + { + return $this->belongsTo(Server::class); + } + + /** + * Gets the domain this subdomain uses. + */ + public function domain(): BelongsTo + { + return $this->belongsTo(Domain::class); + } + + /** + * Get the full domain name (subdomain + domain). + */ + public function getFullDomainAttribute(): string + { + return $this->subdomain . '.' . $this->domain->name; + } + + /** + * Check if this subdomain has DNS records configured. + */ + public function hasDnsRecords(): bool + { + return !empty($this->dns_records); + } +} \ No newline at end of file diff --git a/app/Models/SessionActivity.php b/app/Models/SessionActivity.php new file mode 100644 index 000000000..df0998e85 --- /dev/null +++ b/app/Models/SessionActivity.php @@ -0,0 +1,53 @@ + 'integer', + 'payload' => 'array', + 'last_activity' => 'integer', + ]; + + /** + * Get the user that owns this session activity. + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} \ No newline at end of file diff --git a/app/Observers/AllocationObserver.php b/app/Observers/AllocationObserver.php new file mode 100644 index 000000000..4a0eafefe --- /dev/null +++ b/app/Observers/AllocationObserver.php @@ -0,0 +1,64 @@ +isDirty(['ip', 'port'])) { + $this->syncSubdomainForAllocation($allocation); + } + } + + /** + * Handle the Allocation "deleting" event. + */ + public function deleting(Allocation $allocation): void + { + // If this is a primary allocation being deleted, sync subdomains + if ($allocation->server && $allocation->server->allocation_id === $allocation->id) { + $this->syncSubdomainForAllocation($allocation); + } + } + + /** + * Sync subdomain DNS records for servers using this allocation. + */ + private function syncSubdomainForAllocation(Allocation $allocation): void + { + if (!$allocation->server) { + return; + } + + // Only sync if this is the primary allocation for the server + if ($allocation->server->allocation_id !== $allocation->id) { + return; + } + + $activeSubdomain = $allocation->server->activeSubdomain; + + if (!$activeSubdomain) { + return; + } + + try { + $this->subdomainService->updateSubdomain($activeSubdomain); + Log::info("Updated DNS records for subdomain {$activeSubdomain->full_domain} due to allocation change"); + } catch (\Exception $e) { + Log::error("Failed to update DNS records for subdomain {$activeSubdomain->full_domain}: {$e->getMessage()}"); + } + } +} \ No newline at end of file diff --git a/app/Observers/EggObserver.php b/app/Observers/EggObserver.php new file mode 100644 index 000000000..d9465f5e6 --- /dev/null +++ b/app/Observers/EggObserver.php @@ -0,0 +1,56 @@ +subdomainService->getServerSubdomainFeature($server); + if (!$feature) { + Log::info("Server {$server->id} does not support subdomains. Features: " . json_encode($server->egg->features)); + return; + } + + // Get default domain + $domain = Domain::getDefault(); + if (!$domain) { + Log::warning("No default domain available for subdomain creation. Please set a default domain in the admin panel."); + return; + } + + if (!$domain->is_active) { + Log::warning("Default domain {$domain->name} is not active"); + return; + } + + // Get existing subdomains for uniqueness check + $existingSubdomains = ServerSubdomain::where('domain_id', $domain->id) + ->where('is_active', true) + ->pluck('subdomain') + ->toArray(); + + // Generate unique subdomain + $subdomain = $this->subdomainGenerator->generateUnique($existingSubdomains); + + try { + $this->subdomainService->createSubdomain($server, $domain, $subdomain); + Log::info("Created subdomain {$subdomain}.{$domain->name} for new server {$server->id}"); + } catch (\Exception $e) { + Log::error("Failed to create subdomain for server {$server->id}: {$e->getMessage()}"); + } } /** - * Listen to the Server deleting event. - */ - public function deleting(Server $server): void - { - event(new Events\Server\Deleting($server)); - } - - /** - * Listen to the Server deleted event. - */ - public function deleted(Server $server): void - { - event(new Events\Server\Deleted($server)); - } - - /** - * Listen to the Server saving event. - */ - public function saving(Server $server): void - { - event(new Events\Server\Saving($server)); - } - - /** - * Listen to the Server saved event. - */ - public function saved(Server $server): void - { - event(new Events\Server\Saved($server)); - } - - /** - * Listen to the Server updating event. - */ - public function updating(Server $server): void - { - event(new Events\Server\Updating($server)); - } - - /** - * Listen to the Server saved event. + * Handle the Server "updated" event. */ public function updated(Server $server): void { - event(new Events\Server\Updated($server)); + // Check if allocation_id changed (primary allocation changed) + if ($server->isDirty('allocation_id')) { + $this->syncSubdomainRecords($server); + } + + // Check if egg_id changed (server software changed) + if ($server->isDirty('egg_id')) { + $this->handleServerSoftwareChange($server); + } + } + + /** + * Handle the Server "deleting" event. + */ + public function deleting(Server $server): void + { + // Delete all subdomains when server is deleted + $subdomains = $server->subdomains()->where('is_active', true)->get(); + + foreach ($subdomains as $subdomain) { + try { + $this->subdomainService->deleteSubdomain($subdomain); + } catch (\Exception $e) { + Log::warning("Failed to delete subdomain {$subdomain->full_domain} for server {$server->id}: {$e->getMessage()}"); + } + } + } + + /** + * Sync subdomain DNS records when server allocation changes. + */ + private function syncSubdomainRecords(Server $server): void + { + $activeSubdomain = $server->activeSubdomain; + + if (!$activeSubdomain) { + return; + } + + try { + $this->subdomainService->updateSubdomain($activeSubdomain); + Log::info("Updated DNS records for subdomain {$activeSubdomain->full_domain} due to server allocation change"); + } catch (\Exception $e) { + Log::error("Failed to update DNS records for subdomain {$activeSubdomain->full_domain}: {$e->getMessage()}"); + } + } + + /** + * Handle server software changes. + */ + private function handleServerSoftwareChange(Server $server): void + { + $activeSubdomain = $server->activeSubdomain; + + if (!$activeSubdomain) { + return; + } + + // Check if the new egg supports subdomains + $feature = $this->subdomainService->getServerSubdomainFeature($server); + + if (!$feature) { + // New software doesn't support subdomains, delete the subdomain + try { + $this->subdomainService->deleteSubdomain($activeSubdomain); + Log::info("Deleted subdomain {$activeSubdomain->full_domain} because new server software doesn't support subdomains"); + } catch (\Exception $e) { + Log::error("Failed to delete subdomain {$activeSubdomain->full_domain} after software change: {$e->getMessage()}"); + } + } else { + // New software supports subdomains, check if record type changed + $newRecordType = str_replace('subdomain_', '', $feature->getFeatureName()); + if ($newRecordType !== $activeSubdomain->record_type) { + try { + // Delete old records and create new ones + $this->subdomainService->deleteSubdomain($activeSubdomain); + $this->subdomainService->createSubdomain( + $server, + $activeSubdomain->domain, + $activeSubdomain->subdomain + ); + Log::info("Recreated subdomain {$activeSubdomain->full_domain} with new record type due to software change"); + } catch (\Exception $e) { + Log::error("Failed to recreate subdomain {$activeSubdomain->full_domain} after software change: {$e->getMessage()}"); + } + } else { + // Same record type, just update records + try { + $this->subdomainService->updateSubdomain($activeSubdomain); + Log::info("Updated DNS records for subdomain {$activeSubdomain->full_domain} due to server software change"); + } catch (\Exception $e) { + Log::error("Failed to update DNS records for subdomain {$activeSubdomain->full_domain}: {$e->getMessage()}"); + } + } + } } } diff --git a/app/Observers/SessionActivityObserver.php b/app/Observers/SessionActivityObserver.php new file mode 100644 index 000000000..d04c72a04 --- /dev/null +++ b/app/Observers/SessionActivityObserver.php @@ -0,0 +1,56 @@ +app->singleton('extensions.themes', function () { return new Theme(); }); + } /** diff --git a/app/Providers/BladeServiceProvider.php b/app/Providers/BladeServiceProvider.php index 3512acfd9..1bc77c39f 100644 --- a/app/Providers/BladeServiceProvider.php +++ b/app/Providers/BladeServiceProvider.php @@ -2,18 +2,31 @@ namespace Pterodactyl\Providers; +use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; +use Pterodactyl\Services\Captcha\CaptchaManager; class BladeServiceProvider extends ServiceProvider { /** - * Perform post-registration booting of services. + * Register any application services. + */ + public function register(): void + { + // + } + + /** + * Bootstrap any application services. */ public function boot(): void { - $this->app->make('blade.compiler') - ->directive('datetimeHuman', function ($expression) { - return "setTimezone(config('app.timezone'))->toDateTimeString(); ?>"; - }); + Blade::directive('captcha', function ($form) { + return "getWidget($form); ?>"; + }); + + Blade::if('captchaEnabled', function () { + return app(CaptchaManager::class)->getDefaultDriver() !== 'none'; + }); } } diff --git a/app/Providers/CaptchaServiceProvider.php b/app/Providers/CaptchaServiceProvider.php new file mode 100644 index 000000000..78fca10a6 --- /dev/null +++ b/app/Providers/CaptchaServiceProvider.php @@ -0,0 +1,30 @@ +app->singleton(CaptchaManager::class, function ($app) { + return new CaptchaManager($app, $app->make(SettingsRepositoryInterface::class)); + }); + + $this->app->alias(CaptchaManager::class, 'captcha'); + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // + } +} \ No newline at end of file diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 6f91f24f5..1428af57b 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -3,11 +3,9 @@ namespace Pterodactyl\Providers; use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; use Pterodactyl\Models\EggVariable; use Pterodactyl\Observers\UserObserver; -use Pterodactyl\Observers\ServerObserver; use Pterodactyl\Observers\SubuserObserver; use Pterodactyl\Observers\EggVariableObserver; use Pterodactyl\Listeners\Auth\AuthenticationListener; @@ -36,7 +34,6 @@ class EventServiceProvider extends ServiceProvider parent::boot(); User::observe(UserObserver::class); - Server::observe(ServerObserver::class); Subuser::observe(SubuserObserver::class); EggVariable::observe(EggVariableObserver::class); } diff --git a/app/Providers/ObserverServiceProvider.php b/app/Providers/ObserverServiceProvider.php new file mode 100644 index 000000000..d5c6efba8 --- /dev/null +++ b/app/Providers/ObserverServiceProvider.php @@ -0,0 +1,44 @@ +app['router']; + $router->aliasMiddleware('server.operation.rate-limit', ServerOperationRateLimit::class); + + $this->publishes([ + __DIR__ . '/../../config/server_operations.php' => config_path('server_operations.php'), + ], 'server-operations-config'); + } +} \ No newline at end of file diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index dbcca85ea..79cc1c078 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -12,102 +12,111 @@ use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface; class SettingsServiceProvider extends ServiceProvider { - /** - * An array of configuration keys to override with database values - * if they exist. - */ - protected array $keys = [ - 'app:name', - 'app:locale', - 'recaptcha:enabled', - 'recaptcha:secret_key', - 'recaptcha:website_key', - 'pterodactyl:guzzle:timeout', - 'pterodactyl:guzzle:connect_timeout', - 'pterodactyl:console:count', - 'pterodactyl:console:frequency', - 'pterodactyl:auth:2fa_required', - 'pterodactyl:client_features:allocations:enabled', - 'pterodactyl:client_features:allocations:range_start', - 'pterodactyl:client_features:allocations:range_end', - ]; + /** + * An array of configuration keys to override with database values + * if they exist. + */ + protected array $keys = [ + 'app:name', + 'app:locale', + // existing mail keys, etc... + 'pterodactyl:guzzle:timeout', + 'pterodactyl:guzzle:connect_timeout', + 'pterodactyl:console:count', + 'pterodactyl:console:frequency', + 'pterodactyl:auth:2fa_required', + 'pterodactyl:client_features:allocations:enabled', + 'pterodactyl:client_features:allocations:range_start', + 'pterodactyl:client_features:allocations:range_end', + 'pterodactyl:captcha:provider', + 'pterodactyl:captcha:turnstile:site_key', + 'pterodactyl:captcha:turnstile:secret_key', + 'pterodactyl:captcha:hcaptcha:site_key', + 'pterodactyl:captcha:hcaptcha:secret_key', + 'pterodactyl:captcha:recaptcha:site_key', + 'pterodactyl:captcha:recaptcha:secret_key', + ]; - /** - * Keys specific to the mail driver that are only grabbed from the database - * when using the SMTP driver. - */ - protected array $emailKeys = [ - 'mail:mailers:smtp:host', - 'mail:mailers:smtp:port', - 'mail:mailers:smtp:encryption', - 'mail:mailers:smtp:username', - 'mail:mailers:smtp:password', - 'mail:from:address', - 'mail:from:name', - ]; - /** - * Keys that are encrypted and should be decrypted when set in the - * configuration array. - */ - protected static array $encrypted = [ - 'mail:mailers:smtp:password', - ]; + /** + * Keys specific to the mail driver that are only grabbed from the database + * when using the SMTP driver. + */ + protected array $emailKeys = [ + 'mail:mailers:smtp:host', + 'mail:mailers:smtp:port', + 'mail:mailers:smtp:encryption', + 'mail:mailers:smtp:username', + 'mail:mailers:smtp:password', + 'mail:from:address', + 'mail:from:name', + ]; - /** - * Boot the service provider. - */ - public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings): void - { - // Only set the email driver settings from the database if we - // are configured using SMTP as the driver. - if ($config->get('mail.default') === 'smtp') { - $this->keys = array_merge($this->keys, $this->emailKeys); - } + /** + * Keys that are encrypted and should be decrypted when set in the + * configuration array. + */ + protected static array $encrypted = [ + 'mail:mailers:smtp:password', + 'pterodactyl:captcha:turnstile:secret_key', + 'pterodactyl:captcha:hcaptcha:secret_key', + 'pterodactyl:captcha:recaptcha:secret_key', + ]; + /** + * Boot the service provider. + */ + public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings): void + { + // Only set the email driver settings from the database if we + // are configured using SMTP as the driver. + if ($config->get('mail.default') === 'smtp') { + $this->keys = array_merge($this->keys, $this->emailKeys); + } + + try { + $values = $settings->all()->mapWithKeys(function ($setting) { + return [$setting->key => $setting->value]; + })->toArray(); + } catch (QueryException $exception) { + $log->notice('A query exception was encountered while trying to load settings from the database: ' . $exception->getMessage()); + + return; + } + + foreach ($this->keys as $key) { + $value = array_get($values, 'settings::' . $key, $config->get(str_replace(':', '.', $key))); + if (in_array($key, self::$encrypted)) { try { - $values = $settings->all()->mapWithKeys(function ($setting) { - return [$setting->key => $setting->value]; - })->toArray(); - } catch (QueryException $exception) { - $log->notice('A query exception was encountered while trying to load settings from the database: ' . $exception->getMessage()); - - return; + $value = $encrypter->decrypt($value); + } catch (DecryptException $exception) { } + } - foreach ($this->keys as $key) { - $value = array_get($values, 'settings::' . $key, $config->get(str_replace(':', '.', $key))); - if (in_array($key, self::$encrypted)) { - try { - $value = $encrypter->decrypt($value); - } catch (DecryptException $exception) { - } - } + switch (strtolower($value)) { + case 'true': + case '(true)': + $value = true; + break; + case 'false': + case '(false)': + $value = false; + break; + case 'empty': + case '(empty)': + $value = ''; + break; + case 'null': + case '(null)': + $value = null; + } - switch (strtolower($value)) { - case 'true': - case '(true)': - $value = true; - break; - case 'false': - case '(false)': - $value = false; - break; - case 'empty': - case '(empty)': - $value = ''; - break; - case 'null': - case '(null)': - $value = null; - } - - $config->set(str_replace(':', '.', $key), $value); - } + $config->set(str_replace(':', '.', $key), $value); } + } - public static function getEncryptedKeys(): array - { - return self::$encrypted; - } + public static function getEncryptedKeys(): array + { + return self::$encrypted; + } } diff --git a/app/Providers/SubdomainServiceProvider.php b/app/Providers/SubdomainServiceProvider.php new file mode 100644 index 000000000..d364d6c36 --- /dev/null +++ b/app/Providers/SubdomainServiceProvider.php @@ -0,0 +1,27 @@ +app->singleton(SubdomainManagementService::class, function ($app) { + return new SubdomainManagementService(); + }); + } + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + // + } +} \ No newline at end of file diff --git a/app/Repositories/Eloquent/BackupRepository.php b/app/Repositories/Eloquent/BackupRepository.php index bbc5d2cd9..49cd36ca9 100644 --- a/app/Repositories/Eloquent/BackupRepository.php +++ b/app/Repositories/Eloquent/BackupRepository.php @@ -42,4 +42,16 @@ class BackupRepository extends EloquentRepository ->orWhere('is_successful', true); }); } + + /** + * Returns backups that are currently in progress for a specific server. + */ + public function getBackupsInProgress(int $serverId): Collection + { + return $this->getBuilder() + ->where('server_id', $serverId) + ->whereNull('completed_at') + ->get() + ->toBase(); + } } diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php index 066f6d2fa..b7c6b52ea 100644 --- a/app/Repositories/Eloquent/LocationRepository.php +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -23,7 +23,45 @@ class LocationRepository extends EloquentRepository implements LocationRepositor */ public function getAllWithDetails(): Collection { - return $this->getBuilder()->withCount('nodes', 'servers')->get($this->getColumns()); + $locations = $this->getBuilder()->withCount('nodes', 'servers')->get($this->getColumns()); + + foreach ($locations as $location) { + $nodes = $location->nodes()->with('servers')->get(); + + $totalMemory = 0; + $allocatedMemory = 0; + $totalDisk = 0; + $allocatedDisk = 0; + + foreach ($nodes as $node) { + $baseMemoryLimit = $node->memory; + $baseDiskLimit = $node->disk; + $memoryLimit = $baseMemoryLimit * (1 + ($node->memory_overallocate / 100)); + $diskLimit = $baseDiskLimit * (1 + ($node->disk_overallocate / 100)); + + $totalMemory += $memoryLimit; + $totalDisk += $diskLimit; + + $nodeAllocatedMemory = $node->servers->where('exclude_from_resource_calculation', false)->sum('memory'); + $nodeAllocatedDisk = $node->servers->where('exclude_from_resource_calculation', false)->sum('disk'); + + $allocatedMemory += $nodeAllocatedMemory; + $allocatedDisk += $nodeAllocatedDisk; + } + + $totalBaseMemory = $nodes->sum('memory'); + $totalBaseDisk = $nodes->sum('disk'); + + $location->memory_percent = $totalBaseMemory > 0 ? ($allocatedMemory / $totalBaseMemory) * 100 : 0; + $location->disk_percent = $totalBaseDisk > 0 ? ($allocatedDisk / $totalBaseDisk) * 100 : 0; + + $location->total_memory = $totalMemory; + $location->allocated_memory = $allocatedMemory; + $location->total_disk = $totalDisk; + $location->allocated_disk = $allocatedDisk; + } + + return $locations; } /** diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 73915a14e..14d13dc3c 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -25,16 +25,18 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') ->join('servers', 'servers.node_id', '=', 'nodes.id') ->where('node_id', '=', $node->id) + ->where('servers.exclude_from_resource_calculation', '=', false) ->first(); return Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory]) ->mapWithKeys(function ($value, $key) use ($node) { - $maxUsage = $node->{$key}; + $baseLimit = $node->{$key}; + $maxUsage = $baseLimit; if ($node->{$key . '_overallocate'} > 0) { - $maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); + $maxUsage = $baseLimit * (1 + ($node->{$key . '_overallocate'} / 100)); } - $percent = ($value / $maxUsage) * 100; + $percent = ($value / $baseLimit) * 100; return [ $key => [ @@ -55,18 +57,23 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa { $stats = $this->getBuilder()->select( $this->getBuilder()->raw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') - )->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first(); + )->join('servers', 'servers.node_id', '=', 'nodes.id') + ->where('node_id', $node->id) + ->where('servers.exclude_from_resource_calculation', '=', false) + ->first(); return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) { - $maxUsage = $node->{$key}; + $baseLimit = $node->{$key}; + $maxUsage = $baseLimit; if ($node->{$key . '_overallocate'} > 0) { - $maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); + $maxUsage = $baseLimit * (1 + ($node->{$key . '_overallocate'} / 100)); } return [ $key => [ 'value' => $value, 'max' => $maxUsage, + 'base_limit' => $baseLimit, ], ]; })->toArray(); @@ -144,7 +151,10 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa $instance = $this->getBuilder() ->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate']) ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') - ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') + ->leftJoin('servers', function($join) { + $join->on('servers.node_id', '=', 'nodes.id') + ->where('servers.exclude_from_resource_calculation', '=', false); + }) ->where('nodes.id', $node_id); return $instance->first(); diff --git a/app/Repositories/Wings/DaemonBackupRepository.php b/app/Repositories/Wings/DaemonBackupRepository.php index 228784089..b568339bb 100644 --- a/app/Repositories/Wings/DaemonBackupRepository.php +++ b/app/Repositories/Wings/DaemonBackupRepository.php @@ -54,10 +54,11 @@ class DaemonBackupRepository extends DaemonRepository /** * Sends a request to Wings to begin restoring a backup for a server. + * Always truncates the directory for a clean restore. * * @throws DaemonConnectionException */ - public function restore(Backup $backup, ?string $url = null, bool $truncate = false): ResponseInterface + public function restore(Backup $backup, ?string $url = null): ResponseInterface { Assert::isInstanceOf($this->server, Server::class); @@ -67,7 +68,7 @@ class DaemonBackupRepository extends DaemonRepository [ 'json' => [ 'adapter' => $backup->disk, - 'truncate_directory' => $truncate, + 'truncate_directory' => true, 'download_url' => $url ?? '', ], ] diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index 979f74bb9..9048c851a 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -18,7 +18,7 @@ class AssignmentService public const CIDR_MIN_BITS = 32; public const PORT_FLOOR = 1024; public const PORT_CEIL = 65535; - public const PORT_RANGE_LIMIT = 1000; + public const PORT_RANGE_LIMIT = 100000; public const PORT_RANGE_REGEX = '/^(\d{4,5})-(\d{4,5})$/'; /** @@ -52,6 +52,12 @@ class AssignmentService // an array of records, which is not ideal for this use case, we need a SINGLE // IP to use, not multiple. $underlying = gethostbyname($data['allocation_ip']); + + // Validate that gethostbyname returned a valid IP + if (!filter_var($underlying, FILTER_VALIDATE_IP)) { + throw new DisplayException("gethostbyname returned invalid IP address: {$underlying} for input: {$data['allocation_ip']}"); + } + $parsed = Network::parse($underlying); } catch (\Exception $exception) { /* @noinspection PhpUndefinedVariableInspection */ @@ -78,9 +84,16 @@ class AssignmentService } foreach ($block as $unit) { + $ipString = $ip->__toString(); + + // Validate the IP string before insertion + if (!filter_var($ipString, FILTER_VALIDATE_IP)) { + throw new DisplayException("Invalid IP address generated: {$ipString}"); + } + $insertData[] = [ 'node_id' => $node->id, - 'ip' => $ip->__toString(), + 'ip' => $ipString, 'port' => (int) $unit, 'ip_alias' => array_get($data, 'allocation_alias'), 'server_id' => null, @@ -91,9 +104,16 @@ class AssignmentService throw new PortOutOfRangeException(); } + $ipString = $ip->__toString(); + + // Validate the IP string before insertion + if (!filter_var($ipString, FILTER_VALIDATE_IP)) { + throw new DisplayException("Invalid IP address generated: {$ipString}"); + } + $insertData[] = [ 'node_id' => $node->id, - 'ip' => $ip->__toString(), + 'ip' => $ipString, 'port' => (int) $port, 'ip_alias' => array_get($data, 'allocation_alias'), 'server_id' => null, diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php index ffc15d044..82204498b 100644 --- a/app/Services/Allocations/FindAssignableAllocationService.php +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -34,19 +34,41 @@ class FindAssignableAllocationService throw new AutoAllocationNotEnabledException(); } + // Validate that the server has a valid primary allocation IP + if (!$server->allocation) { + throw new \Pterodactyl\Exceptions\DisplayException("Server has no primary allocation"); + } + + $allocationIp = $server->allocation->ip; + + // If it's not a valid IP, try to resolve it as a hostname + if (!filter_var($allocationIp, FILTER_VALIDATE_IP)) { + $resolvedIp = gethostbyname($allocationIp); + + // If gethostbyname fails, it returns the original hostname + if ($resolvedIp === $allocationIp || !filter_var($resolvedIp, FILTER_VALIDATE_IP)) { + throw new \Pterodactyl\Exceptions\DisplayException( + "Cannot resolve allocation IP/hostname '{$allocationIp}' to a valid IP address" + ); + } + + // Use the resolved IP for allocation operations + $allocationIp = $resolvedIp; + } + // Attempt to find a given available allocation for a server. If one cannot be found // we will fall back to attempting to create a new allocation that can be used for the // server. /** @var Allocation|null $allocation */ $allocation = $server->node->allocations() - ->where('ip', $server->allocation->ip) + ->where('ip', $allocationIp) ->whereNull('server_id') ->inRandomOrder() ->first(); - $allocation = $allocation ?? $this->createNewAllocation($server); + $allocation = $allocation ?? $this->createNewAllocation($server, $allocationIp); - $allocation->update(['server_id' => $server->id]); + $allocation->skipValidation()->update(['server_id' => $server->id]); return $allocation->refresh(); } @@ -62,7 +84,7 @@ class FindAssignableAllocationService * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - protected function createNewAllocation(Server $server): Allocation + protected function createNewAllocation(Server $server, string $resolvedIp): Allocation { $start = config('pterodactyl.client_features.allocations.range_start', null); $end = config('pterodactyl.client_features.allocations.range_end', null); @@ -77,7 +99,7 @@ class FindAssignableAllocationService // Get all of the currently allocated ports for the node so that we can figure out // which port might be available. $ports = $server->node->allocations() - ->where('ip', $server->allocation->ip) + ->where('ip', $resolvedIp) ->whereBetween('port', [$start, $end]) ->pluck('port'); @@ -96,13 +118,13 @@ class FindAssignableAllocationService $port = $available[array_rand($available)]; $this->service->handle($server->node, [ - 'allocation_ip' => $server->allocation->ip, + 'allocation_ip' => $resolvedIp, 'allocation_ports' => [$port], ]); /** @var Allocation $allocation */ $allocation = $server->node->allocations() - ->where('ip', $server->allocation->ip) + ->where('ip', $resolvedIp) ->where('port', $port) ->firstOrFail(); diff --git a/app/Services/Backups/DeleteBackupService.php b/app/Services/Backups/DeleteBackupService.php index b77ac8e1c..d6e8c5775 100644 --- a/app/Services/Backups/DeleteBackupService.php +++ b/app/Services/Backups/DeleteBackupService.php @@ -68,15 +68,29 @@ class DeleteBackupService protected function deleteFromS3(Backup $backup): void { $this->connection->transaction(function () use ($backup) { - $backup->delete(); - /** @var \Pterodactyl\Extensions\Filesystem\S3Filesystem $adapter */ $adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3); - $adapter->getClient()->deleteObject([ - 'Bucket' => $adapter->getBucket(), - 'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid), - ]); + $s3Key = sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid); + + // First delete from S3, then from database to prevent orphaned records + try { + $adapter->getClient()->deleteObject([ + 'Bucket' => $adapter->getBucket(), + 'Key' => $s3Key, + ]); + } catch (\Exception $e) { + // Log S3 deletion failure but continue with database cleanup + \Log::warning('Failed to delete backup from S3, continuing with database cleanup', [ + 'backup_uuid' => $backup->uuid, + 'server_uuid' => $backup->server->uuid, + 's3_key' => $s3Key, + 'error' => $e->getMessage(), + ]); + } + + // Delete from database after S3 cleanup + $backup->delete(); }); } } diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php index c00b46c8a..78a27eb08 100644 --- a/app/Services/Backups/InitiateBackupService.php +++ b/app/Services/Backups/InitiateBackupService.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Services\Backups; use Ramsey\Uuid\Uuid; -use Carbon\CarbonImmutable; use Webmozart\Assert\Assert; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; @@ -13,6 +12,7 @@ use Pterodactyl\Repositories\Eloquent\BackupRepository; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; +use Carbon\CarbonImmutable; class InitiateBackupService { @@ -29,6 +29,7 @@ class InitiateBackupService private DaemonBackupRepository $daemonBackupRepository, private DeleteBackupService $deleteBackupService, private BackupManager $backupManager, + private ServerStateService $serverStateService, ) { } @@ -75,15 +76,13 @@ class InitiateBackupService */ public function handle(Server $server, ?string $name = null, bool $override = false): Backup { - $limit = config('backups.throttles.limit'); - $period = config('backups.throttles.period'); - if ($period > 0) { - $previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, $period); - if ($previous->count() >= $limit) { - $message = sprintf('Only %d backups may be generated within a %d second span of time.', $limit, $period); - - throw new TooManyRequestsHttpException((int) CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addSeconds($period)), $message); - } + // Validate server state before creating backup + $this->validateServerForBackup($server); + + // Check for existing backups in progress (only allow one at a time) + $inProgressBackups = $this->repository->getBackupsInProgress($server->id); + if ($inProgressBackups->count() > 0) { + throw new TooManyRequestsHttpException(30, 'A backup is already in progress. Please wait for it to complete before starting another.'); } // Check if the server has reached or exceeded its backup limit. @@ -108,21 +107,57 @@ class InitiateBackupService } return $this->connection->transaction(function () use ($server, $name) { + // Sanitize backup name to prevent injection + $backupName = trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()); + $backupName = preg_replace('/[^a-zA-Z0-9\s\-_\.]/', '', $backupName); + $backupName = substr($backupName, 0, 191); // Limit to database field length + + $serverState = $this->serverStateService->captureServerState($server); + /** @var Backup $backup */ $backup = $this->repository->create([ 'server_id' => $server->id, 'uuid' => Uuid::uuid4()->toString(), - 'name' => trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()), + 'name' => $backupName, 'ignored_files' => array_values($this->ignoredFiles ?? []), 'disk' => $this->backupManager->getDefaultAdapter(), 'is_locked' => $this->isLocked, + 'server_state' => $serverState, ], true, true); - $this->daemonBackupRepository->setServer($server) - ->setBackupAdapter($this->backupManager->getDefaultAdapter()) - ->backup($backup); + try { + $this->daemonBackupRepository->setServer($server) + ->setBackupAdapter($this->backupManager->getDefaultAdapter()) + ->backup($backup); + } catch (\Exception $e) { + // If daemon backup request fails, clean up the backup record + $backup->delete(); + throw $e; + } return $backup; }); } + + /** + * Validate that the server is in a valid state for backup creation + */ + private function validateServerForBackup(Server $server): void + { + if ($server->isSuspended()) { + throw new TooManyBackupsException(0, 'Cannot create backup for suspended server.'); + } + + if (!$server->isInstalled()) { + throw new TooManyBackupsException(0, 'Cannot create backup for server that is not fully installed.'); + } + + if ($server->status === Server::STATUS_RESTORING_BACKUP) { + throw new TooManyBackupsException(0, 'Cannot create backup while server is restoring from another backup.'); + } + + if ($server->transfer) { + throw new TooManyBackupsException(0, 'Cannot create backup while server is being transferred.'); + } + } } diff --git a/app/Services/Backups/ServerStateService.php b/app/Services/Backups/ServerStateService.php new file mode 100644 index 000000000..dad8c1736 --- /dev/null +++ b/app/Services/Backups/ServerStateService.php @@ -0,0 +1,248 @@ +load(['variables', 'egg', 'nest']); + + // Capture basic server configuration + $state = [ + 'nest_id' => $server->nest_id, + 'egg_id' => $server->egg_id, + 'startup' => $server->startup, + 'image' => $server->image, + 'captured_at' => now()->toISOString(), + ]; + + // Capture all server variables with their current values + $variables = []; + foreach ($server->variables as $variable) { + $variables[] = [ + 'variable_id' => $variable->id, + 'env_variable' => $variable->env_variable, + 'name' => $variable->name, + 'description' => $variable->description, + 'default_value' => $variable->default_value, + 'user_viewable' => $variable->user_viewable, + 'user_editable' => $variable->user_editable, + 'rules' => $variable->rules, + 'server_value' => $variable->server_value, // Current configured value + ]; + } + $state['variables'] = $variables; + + // Capture egg information for reference + if ($server->egg) { + $state['egg_info'] = [ + 'name' => $server->egg->name, + 'description' => $server->egg->description, + 'uuid' => $server->egg->uuid, + 'docker_images' => $server->egg->docker_images, + ]; + } + + // Capture nest information for reference + if ($server->nest) { + $state['nest_info'] = [ + 'name' => $server->nest->name, + 'description' => $server->nest->description, + 'uuid' => $server->nest->uuid, + ]; + } + + return $state; + } + + /** + * Restores server state from a backup. + * This will update the server's configuration to match the backup state. + */ + public function restoreServerState(Server $server, Backup $backup): void + { + if (empty($backup->server_state)) { + // Backup doesn't contain server state (backward compatibility) + return; + } + + $state = $backup->server_state; + + $this->connection->transaction(function () use ($server, $state) { + // Update basic server configuration + $serverUpdates = []; + + if (isset($state['nest_id'])) { + $serverUpdates['nest_id'] = $state['nest_id']; + } + + if (isset($state['egg_id'])) { + $serverUpdates['egg_id'] = $state['egg_id']; + } + + if (isset($state['startup'])) { + $serverUpdates['startup'] = $state['startup']; + } + + if (isset($state['image'])) { + $serverUpdates['image'] = $state['image']; + } + + if (!empty($serverUpdates)) { + $server->update($serverUpdates); + } + + // Restore server variables + if (isset($state['variables']) && is_array($state['variables'])) { + $this->restoreServerVariables($server, $state['variables']); + } + }); + } + + /** + * Restores server variables from backup state. + */ + private function restoreServerVariables(Server $server, array $variables): void + { + // First, clear existing server variables to ensure clean state + $this->connection->table('server_variables') + ->where('server_id', $server->id) + ->delete(); + + // Restore variables from backup state + foreach ($variables as $variable) { + if (!isset($variable['env_variable'], $variable['server_value'])) { + continue; // Skip invalid variable data + } + + // Find the current egg variable by environment variable name + // We use env_variable as the key since variable IDs might have changed + $currentEggVariable = $this->connection->table('egg_variables') + ->where('egg_id', $server->egg_id) + ->where('env_variable', $variable['env_variable']) + ->first(); + + if ($currentEggVariable) { + // Insert server variable with the restored value + $this->connection->table('server_variables')->insert([ + 'server_id' => $server->id, + 'variable_id' => $currentEggVariable->id, + 'variable_value' => $variable['server_value'], + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + } + + /** + * Validates if the server state can be restored. + * Checks if the target nest and egg still exist. + */ + public function validateRestoreCompatibility(Backup $backup): array + { + $warnings = []; + $errors = []; + + if (empty($backup->server_state)) { + return ['warnings' => [], 'errors' => []]; + } + + $state = $backup->server_state; + + // Check if nest still exists + if (isset($state['nest_id'])) { + $nestExists = $this->connection->table('nests') + ->where('id', $state['nest_id']) + ->exists(); + + if (!$nestExists) { + $nestName = $state['nest_info']['name'] ?? 'Unknown'; + $errors[] = "Nest '{$nestName}' (ID: {$state['nest_id']}) no longer exists."; + } + } + + // Check if egg still exists + if (isset($state['egg_id'])) { + $eggExists = $this->connection->table('eggs') + ->where('id', $state['egg_id']) + ->exists(); + + if (!$eggExists) { + $eggName = $state['egg_info']['name'] ?? 'Unknown'; + $errors[] = "Egg '{$eggName}' (ID: {$state['egg_id']}) no longer exists."; + } + } + + // Check for missing variables + if (isset($state['variables']) && is_array($state['variables'])) { + $missingVariables = []; + + foreach ($state['variables'] as $variable) { + if (!isset($variable['env_variable'])) continue; + + $exists = $this->connection->table('egg_variables') + ->where('egg_id', $state['egg_id'] ?? 0) + ->where('env_variable', $variable['env_variable']) + ->exists(); + + if (!$exists) { + $missingVariables[] = $variable['name'] ?? $variable['env_variable']; + } + } + + if (!empty($missingVariables)) { + $warnings[] = 'Some variables from the backup no longer exist in the current egg: ' . implode(', ', $missingVariables); + } + } + + return [ + 'warnings' => $warnings, + 'errors' => $errors, + ]; + } + + /** + * Checks if a backup has server state data. + */ + public function hasServerState(Backup $backup): bool + { + return !empty($backup->server_state); + } + + /** + * Gets a summary of the server state for display purposes. + */ + public function getStateSummary(Backup $backup): ?array + { + if (!$this->hasServerState($backup)) { + return null; + } + + $state = $backup->server_state; + + return [ + 'nest_name' => $state['nest_info']['name'] ?? 'Unknown', + 'egg_name' => $state['egg_info']['name'] ?? 'Unknown', + 'image' => $state['image'] ?? 'Unknown', + 'variables_count' => count($state['variables'] ?? []), + 'captured_at' => $state['captured_at'] ?? null, + ]; + } +} \ No newline at end of file diff --git a/app/Services/Captcha/CaptchaManager.php b/app/Services/Captcha/CaptchaManager.php new file mode 100644 index 000000000..e62a30848 --- /dev/null +++ b/app/Services/Captcha/CaptchaManager.php @@ -0,0 +1,106 @@ +settings = $settings; + } + + /** + * Get the default driver name. + */ + public function getDefaultDriver(): string + { + return $this->settings->get('settings::pterodactyl:captcha:provider', 'none'); + } + + /** + * Create the null captcha driver (no captcha). + */ + public function createNoneDriver(): NullProvider + { + return new NullProvider(); + } + + /** + * Create the Turnstile captcha driver. + */ + public function createTurnstileDriver(): TurnstileProvider + { + return new TurnstileProvider([ + 'site_key' => config('pterodactyl.captcha.turnstile.site_key', ''), + 'secret_key' => config('pterodactyl.captcha.turnstile.secret_key', ''), + ]); + } + + /** + * Create the hCaptcha captcha driver. + */ + public function createHcaptchaDriver(): HCaptchaProvider + { + return new HCaptchaProvider([ + 'site_key' => config('pterodactyl.captcha.hcaptcha.site_key', ''), + 'secret_key' => config('pterodactyl.captcha.hcaptcha.secret_key', ''), + ]); + } + + /** + * Create the reCAPTCHA captcha driver. + */ + public function createRecaptchaDriver(): RecaptchaProvider + { + return new RecaptchaProvider([ + 'site_key' => config('pterodactyl.captcha.recaptcha.site_key', ''), + 'secret_key' => config('pterodactyl.captcha.recaptcha.secret_key', ''), + ]); + } + + /** + * Get the captcha widget HTML. + */ + public function getWidget(): string + { + if ($this->getDefaultDriver() === 'none') { + return ''; + } + + return $this->driver()->getWidget('default'); + } + + /** + * Verify a captcha response. + */ + public function verify(string $response, ?string $remoteIp = null): bool + { + if ($this->getDefaultDriver() === 'none') { + return true; + } + + return $this->driver()->verify($response, $remoteIp); + } + + /** + * Get the JavaScript includes needed for the captcha. + */ + public function getScriptIncludes(): array + { + if ($this->getDefaultDriver() === 'none') { + return []; + } + + return $this->driver()->getScriptIncludes(); + } +} \ No newline at end of file diff --git a/app/Services/Captcha/Providers/HCaptchaProvider.php b/app/Services/Captcha/Providers/HCaptchaProvider.php new file mode 100644 index 000000000..f8963a1f6 --- /dev/null +++ b/app/Services/Captcha/Providers/HCaptchaProvider.php @@ -0,0 +1,128 @@ +siteKey = $config['site_key'] ?? ''; + $this->secretKey = $config['secret_key'] ?? ''; + } + + /** + * Get the HTML widget for the captcha. + */ + public function getWidget(string $form): string + { + if (empty($this->siteKey)) { + return ''; + } + + return sprintf( + '
', + htmlspecialchars($this->siteKey, ENT_QUOTES, 'UTF-8') + ); + } + + /** + * Verify a captcha response. + */ + public function verify(string $response, ?string $remoteIp = null): bool + { + if (empty($this->secretKey) || empty($response)) { + return false; + } + + try { + $data = [ + 'secret' => $this->secretKey, + 'response' => $response, + ]; + + if ($remoteIp) { + $data['remoteip'] = $remoteIp; + } + + $httpResponse = Http::timeout(10) + ->asForm() + ->post($this->verifyUrl, $data); + + if (!$httpResponse->successful()) { + Log::warning('hCaptcha verification failed: HTTP ' . $httpResponse->status()); + return false; + } + + $result = $httpResponse->json(); + + if (!isset($result['success'])) { + Log::warning('hCaptcha verification failed: Invalid response format'); + return false; + } + + if (!$result['success'] && isset($result['error-codes'])) { + Log::warning('hCaptcha verification failed', [ + 'error_codes' => $result['error-codes'], + ]); + } + + return (bool) $result['success']; + } catch (\Exception $e) { + Log::error('hCaptcha verification exception', [ + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + return false; + } + } + + /** + * Get the JavaScript includes needed for this captcha provider. + */ + public function getScriptIncludes(): array + { + return [ + 'https://js.hcaptcha.com/1/api.js', + ]; + } + + /** + * Get the provider name. + */ + public function getName(): string + { + return 'hcaptcha'; + } + + /** + * Get the site key for frontend use. + */ + public function getSiteKey(): string + { + return $this->siteKey; + } + + /** + * Check if the provider is properly configured. + */ + public function isConfigured(): bool + { + return !empty($this->siteKey) && !empty($this->secretKey); + } + + /** + * Get the response field name for this provider. + */ + public function getResponseFieldName(): string + { + return 'h-captcha-response'; + } +} \ No newline at end of file diff --git a/app/Services/Captcha/Providers/NullProvider.php b/app/Services/Captcha/Providers/NullProvider.php new file mode 100644 index 000000000..5cf9224b7 --- /dev/null +++ b/app/Services/Captcha/Providers/NullProvider.php @@ -0,0 +1,64 @@ +siteKey = $config['site_key'] ?? ''; + $this->secretKey = $config['secret_key'] ?? ''; + } + + public function verify(string $response, ?string $remoteIp = null): bool + { + if (empty($response)) { + return false; + } + + try { + $httpResponse = Http::timeout(10) + ->asForm() + ->post(self::VERIFY_URL, [ + 'secret' => $this->secretKey, + 'response' => $response, + 'remoteip' => $remoteIp, + ]); + + if (!$httpResponse->successful()) { + return false; + } + + $data = $httpResponse->json(); + + // For reCAPTCHA v3, check both success and score + if (!isset($data['success']) || $data['success'] !== true) { + return false; + } + + // reCAPTCHA v3 returns a score (0.0 - 1.0) + // Use 0.5 as default threshold as recommended by Google + $score = $data['score'] ?? 0.0; + $threshold = 0.5; + + return $score >= $threshold; + } catch (\Exception $e) { + return false; + } + } + + public function getWidget(string $form): string + { + // reCAPTCHA v3 doesn't use a visible widget + // Instead, it runs in the background and we execute it programmatically + return sprintf( + ' + ', + htmlspecialchars($this->siteKey, ENT_QUOTES, 'UTF-8'), + htmlspecialchars($form, ENT_QUOTES, 'UTF-8') + ); + } + + public function getScriptIncludes(): array + { + return [sprintf('https://www.google.com/recaptcha/api.js?render=%s', $this->siteKey)]; + } + + public function getName(): string + { + return 'recaptcha'; + } + + public function getResponseFieldName(): string + { + return 'g-recaptcha-response'; + } + + public function getSiteKey(): string + { + return $this->siteKey; + } + + /** + * Check if the provider is properly configured. + */ + public function isConfigured(): bool + { + return !empty($this->siteKey) && !empty($this->secretKey); + } +} \ No newline at end of file diff --git a/app/Services/Captcha/Providers/TurnstileProvider.php b/app/Services/Captcha/Providers/TurnstileProvider.php new file mode 100644 index 000000000..ce4c27067 --- /dev/null +++ b/app/Services/Captcha/Providers/TurnstileProvider.php @@ -0,0 +1,162 @@ +siteKey = $config['site_key'] ?? ''; + $this->secretKey = $config['secret_key'] ?? ''; + } + + /** + * Get the HTML widget for the captcha. + */ + public function getWidget(string $form): string + { + if (empty($this->siteKey)) { + return ''; + } + + return sprintf( + '
', + htmlspecialchars($this->siteKey, ENT_QUOTES, 'UTF-8') + ); + } + + /** + * Verify a captcha response. + */ + public function verify(string $response, ?string $remoteIp = null): bool + { + Log::info('Turnstile verification attempt', [ + 'response_length' => strlen($response), + 'response_preview' => substr($response, 0, 50) . '...', + 'remote_ip' => $remoteIp, + 'secret_key_set' => !empty($this->secretKey), + 'site_key' => $this->siteKey, + ]); + + if (empty($this->secretKey) || empty($response)) { + Log::warning('Turnstile verification failed: Missing secret key or response', [ + 'secret_key_empty' => empty($this->secretKey), + 'response_empty' => empty($response), + ]); + return false; + } + + try { + $data = [ + 'secret' => $this->secretKey, + 'response' => $response, + ]; + + if ($remoteIp) { + $data['remoteip'] = $remoteIp; + } + + Log::info('Sending Turnstile verification request', [ + 'url' => $this->verifyUrl, + 'data_keys' => array_keys($data), + 'remote_ip' => $remoteIp, + ]); + + $httpResponse = Http::timeout(10) + ->asForm() + ->post($this->verifyUrl, $data); + + Log::info('Turnstile verification response', [ + 'status' => $httpResponse->status(), + 'successful' => $httpResponse->successful(), + 'body' => $httpResponse->body(), + ]); + + if (!$httpResponse->successful()) { + Log::warning('Turnstile verification failed: HTTP ' . $httpResponse->status(), [ + 'response_body' => $httpResponse->body(), + ]); + return false; + } + + $result = $httpResponse->json(); + + if (!isset($result['success'])) { + Log::warning('Turnstile verification failed: Invalid response format', [ + 'result' => $result, + ]); + return false; + } + + if (!$result['success'] && isset($result['error-codes'])) { + Log::warning('Turnstile verification failed', [ + 'error_codes' => $result['error-codes'], + 'full_result' => $result, + ]); + } + + Log::info('Turnstile verification result', [ + 'success' => $result['success'], + 'full_result' => $result, + ]); + + return (bool) $result['success']; + } catch (\Exception $e) { + Log::error('Turnstile verification exception', [ + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + return false; + } + } + + /** + * Get the JavaScript includes needed for this captcha provider. + */ + public function getScriptIncludes(): array + { + return [ + 'https://challenges.cloudflare.com/turnstile/v0/api.js', + ]; + } + + /** + * Get the provider name. + */ + public function getName(): string + { + return 'turnstile'; + } + + /** + * Get the site key for frontend use. + */ + public function getSiteKey(): string + { + return $this->siteKey; + } + + /** + * Check if the provider is properly configured. + */ + public function isConfigured(): bool + { + return !empty($this->siteKey) && !empty($this->secretKey); + } + + /** + * Get the response field name for this provider. + */ + public function getResponseFieldName(): string + { + return 'cf-turnstile-response'; + } +} \ No newline at end of file diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index cc76ad718..8612cbfa0 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -2,93 +2,84 @@ namespace Pterodactyl\Services\Deployment; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection as EloquentCollection; +use Illuminate\Support\Collection; +use Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException; use Pterodactyl\Models\Node; use Webmozart\Assert\Assert; -use Illuminate\Support\Collection; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; -use Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException; +/** + * Finds nodes that can host a new server, always preferring *physical* capacity + * over overallocation. The search runs in two passes with different ordering strategies: + * + * Pass 1 (Physical Capacity) + * Ignore memory/disk overallocation completely. If any node can fit + * the server within its physical limit, choose the best-fit result(s) + * (least leftover capacity first) for efficient resource usage. + * + * Pass 2 (Overallocation Fallback) + * Only executed when Pass 1 returns zero rows. Re-runs the query but + * includes each node's configured overallocation allowances. Uses + * worst-fit ordering (most leftover capacity first) to spread load + * across nodes when overallocation is involved. + * + */ class FindViableNodesService { - protected array $locations = []; - protected ?int $disk = null; - protected ?int $memory = null; + private array $locations = []; + private ?int $memory = null; + private ?int $disk = null; - /** - * Set the locations that should be searched through to locate available nodes. - */ + /** Default paginator size when `$perPage` is omitted. */ + public const DEFAULT_PER_PAGE = 50; + + /** Restrict the search to these location IDs. */ public function setLocations(array $locations): self { - Assert::allIntegerish($locations, 'An array of location IDs should be provided when calling setLocations.'); + Assert::allIntegerish( + $locations, + 'An array of location IDs should be provided when calling setLocations.' + ); - $this->locations = $locations; + $this->locations = array_unique($locations); return $this; } - /** - * Set the amount of disk that will be used by the server being created. Nodes will be - * filtered out if they do not have enough available free disk space for this server - * to be placed on. - */ - public function setDisk(int $disk): self - { - $this->disk = $disk; - - return $this; - } - - /** - * Set the amount of memory that this server will be using. As with disk space, nodes that - * do not have enough free memory will be filtered out. - */ + /** Memory (in MB) required by the server being provisioned. */ public function setMemory(int $memory): self { $this->memory = $memory; + return $this; + } + /** Disk (in MB) required by the server being provisioned. */ + public function setDisk(int $disk): self + { + $this->disk = $disk; return $this; } /** - * Returns an array of nodes that meet the provided requirements and can then - * be passed to the AllocationSelectionService to return a single allocation. - * - * This functionality is used for automatic deployments of servers and will - * attempt to find all nodes in the defined locations that meet the disk and - * memory availability requirements. Any nodes not meeting those requirements - * are tossed out, as are any nodes marked as non-public, meaning automatic - * deployments should not be done against them. - * - * @param int|null $page If provided the results will be paginated by returning - * up to 50 nodes at a time starting at the provided page. - * If "null" is provided as the value no pagination will - * be used. + * Locate viable nodes using the two-pass strategy outlined above. * * @throws NoViableNodeException */ public function handle(?int $perPage = null, ?int $page = null): LengthAwarePaginator|Collection { - Assert::integer($this->disk, 'Disk space must be an int, got %s'); - Assert::integer($this->memory, 'Memory usage must be an int, got %s'); + Assert::integer($this->disk, 'Disk space must be an int, got NULL'); + Assert::integer($this->memory,'Memory usage must be an int, got NULL'); - $query = Node::query()->select('nodes.*') - ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory') - ->selectRaw('IFNULL(SUM(servers.disk), 0) as sum_disk') - ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') - ->where('nodes.public', 1); + $perPage = $perPage ?? self::DEFAULT_PER_PAGE; - if (!empty($this->locations)) { - $query = $query->whereIn('nodes.location_id', $this->locations); - } + // Pass 1: physical capacity only + $results = $this->runQuery(false, $perPage, $page); - $results = $query->groupBy('nodes.id') - ->havingRaw('(IFNULL(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1 + (nodes.memory_overallocate / 100)))', [$this->memory]) - ->havingRaw('(IFNULL(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1 + (nodes.disk_overallocate / 100)))', [$this->disk]); - - if (!is_null($page)) { - $results = $results->paginate($perPage ?? 50, ['*'], 'page', $page); - } else { - $results = $results->get()->toBase(); + // Pass 2: allow overallocation if Pass 1 failed + if ($results->isEmpty()) { + $results = $this->runQuery(true, $perPage, $page); } if ($results->isEmpty()) { @@ -97,4 +88,70 @@ class FindViableNodesService return $results; } + + /** + * Build and execute a query. + * + * @param bool $allowOverallocation Treat overallocate percentage as + * extra capacity when true. + * @param int|null $perPage Paginator size (null = no paging). + * @param int|null $page Page number when paginating. + */ + private function runQuery( + bool $allowOverallocation, + ?int $perPage, + ?int $page + ): LengthAwarePaginator|Collection { + $memCap = $allowOverallocation + ? '(nodes.memory * (1 + nodes.memory_overallocate / 100.0))' + : 'nodes.memory'; + + $diskCap = $allowOverallocation + ? '(nodes.disk * (1 + nodes.disk_overallocate / 100.0))' + : 'nodes.disk'; + + /** @var Builder $query */ + $query = Node::query() + ->select('nodes.*') + ->selectRaw( + "$memCap - COALESCE(SUM(servers.memory), 0) AS free_memory, " . + "$diskCap - COALESCE(SUM(servers.disk), 0) AS free_disk" + ) + ->leftJoin('servers', function ($join) { + $join->on('servers.node_id', '=', 'nodes.id') + ->where('servers.exclude_from_resource_calculation', '=', false); + }) + ->where('nodes.public', true) + ->when( + $this->locations !== [], + fn (Builder $q) => $q->whereIn('nodes.location_id', $this->locations) + ) + ->groupBy('nodes.id') + + // Capacity filters + ->havingRaw("$memCap - COALESCE(SUM(servers.memory), 0) >= ?", [$this->memory]) + ->havingRaw("$diskCap - COALESCE(SUM(servers.disk), 0) >= ?", [$this->disk]) + + // Ordering strategy: best-fit for physical capacity, worst-fit for overallocation + ->orderByRaw( + "($memCap - COALESCE(SUM(servers.memory), 0) + " . + "$diskCap - COALESCE(SUM(servers.disk), 0)) " . + ($allowOverallocation ? 'DESC' : 'ASC') + ); + + // Execute and strip helper columns + $results = $page !== null + ? /** @var LengthAwarePaginator $paginator */ $query->paginate($perPage, ['*'], 'page', $page) + : /** @var EloquentCollection $collection */ $query->get(); + + $strip = static fn (Node $node) => $node->setHidden(['free_memory', 'free_disk']); + + if ($results instanceof LengthAwarePaginator) { + $results->getCollection()->each($strip); + } else { + $results = $results->map($strip); + } + + return $results; + } } diff --git a/app/Services/Dns/Providers/CloudflareProvider.php b/app/Services/Dns/Providers/CloudflareProvider.php new file mode 100644 index 000000000..cf0f1fa4c --- /dev/null +++ b/app/Services/Dns/Providers/CloudflareProvider.php @@ -0,0 +1,279 @@ +config = $config; + + // Only initialize the client if we have an API token + if (!empty($config['api_token'])) { + $this->client = new Client([ + 'base_uri' => 'https://api.cloudflare.com/client/v4/', + 'headers' => [ + 'Authorization' => 'Bearer ' . $config['api_token'], + 'Content-Type' => 'application/json', + ], + 'timeout' => 30, + ]); + } + } + + /** + * Test the connection to Cloudflare API. + */ + public function testConnection(): bool + { + if (!isset($this->client)) { + throw DnsProviderException::invalidConfiguration('cloudflare', 'api_token'); + } + + try { + $response = $this->client->get('user/tokens/verify'); + $data = json_decode($response->getBody()->getContents(), true); + + if (!$data['success']) { + throw DnsProviderException::authenticationFailed('cloudflare'); + } + + return true; + } catch (GuzzleException $e) { + throw DnsProviderException::connectionFailed('cloudflare', $e->getMessage()); + } + } + + /** + * Create a DNS record. + */ + public function createRecord(string $domain, string $name, string $type, $content, int $ttl = 300): string + { + $zoneId = $this->getZoneId($domain); + + try { + $payload = [ + 'type' => strtoupper($type), + 'name' => $name, + 'ttl' => $ttl, + ]; + + // Handle different content types + if (is_array($content)) { + // For SRV records and other structured data + $payload['data'] = $content; + if (isset($content['content'])) { + $payload['content'] = $content['content']; + } + } else { + // For simple records like A, CNAME + $payload['content'] = $content; + } + + $response = $this->client->post("zones/{$zoneId}/dns_records", [ + 'json' => $payload + ]); + + $data = json_decode($response->getBody()->getContents(), true); + + if (!$data['success']) { + throw new \Exception('DNS provider rejected the record creation request.'); + } + + return $data['result']['id']; + } catch (GuzzleException $e) { + throw DnsProviderException::recordCreationFailed($domain, $name, 'DNS service temporarily unavailable.'); + } + } + + /** + * Update a DNS record. + */ + public function updateRecord(string $domain, string $recordId, $content, ?int $ttl = null): bool + { + $zoneId = $this->getZoneId($domain); + + try { + $payload = []; + + // Handle different content types + if (is_array($content)) { + // For SRV records and other structured data + $payload['data'] = $content; + if (isset($content['content'])) { + $payload['content'] = $content['content']; + } + } else { + // For simple records like A, CNAME + $payload['content'] = $content; + } + + if ($ttl !== null) { + $payload['ttl'] = $ttl; + } + + $response = $this->client->patch("zones/{$zoneId}/dns_records/{$recordId}", [ + 'json' => $payload + ]); + + $data = json_decode($response->getBody()->getContents(), true); + + if (!$data['success']) { + throw new \Exception('DNS provider rejected the record update request.'); + } + + return true; + } catch (GuzzleException $e) { + throw DnsProviderException::recordUpdateFailed($domain, [$recordId], 'DNS service temporarily unavailable.'); + } + } + + /** + * Delete a DNS record. + */ + public function deleteRecord(string $domain, string $recordId): void + { + $zoneId = $this->getZoneId($domain); + + try { + $response = $this->client->delete("zones/{$zoneId}/dns_records/{$recordId}"); + $data = json_decode($response->getBody()->getContents(), true); + + if (!$data['success']) { + throw new \Exception('DNS provider rejected the record deletion request.'); + } + } catch (GuzzleException $e) { + throw DnsProviderException::recordDeletionFailed($domain, [$recordId], 'DNS service temporarily unavailable.'); + } + } + + /** + * Get a specific DNS record. + */ + public function getRecord(string $domain, string $recordId): array + { + $zoneId = $this->getZoneId($domain); + + try { + $response = $this->client->get("zones/{$zoneId}/dns_records/{$recordId}"); + $data = json_decode($response->getBody()->getContents(), true); + + if (!$data['success']) { + throw new \Exception("DNS record not found or inaccessible."); + } + + return $data['result']; + } catch (GuzzleException $e) { + throw DnsProviderException::connectionFailed('cloudflare', 'DNS service temporarily unavailable.'); + } + } + + /** + * List existing DNS records for a domain. + */ + public function listRecords(string $domain, ?string $name = null, ?string $type = null): array + { + $zoneId = $this->getZoneId($domain); + + try { + $params = []; + if ($name) { + $params['name'] = $name; + } + if ($type) { + $params['type'] = strtoupper($type); + } + + $response = $this->client->get("zones/{$zoneId}/dns_records", [ + 'query' => $params + ]); + + $data = json_decode($response->getBody()->getContents(), true); + + if (!$data['success']) { + throw new \Exception('Failed to retrieve DNS records.'); + } + + return $data['result']; + } catch (GuzzleException $e) { + throw DnsProviderException::connectionFailed('cloudflare', 'DNS service temporarily unavailable.'); + } + } + + /** + * Get the configuration schema for Cloudflare. + */ + public function getConfigurationSchema(): array + { + return [ + 'api_token' => [ + 'type' => 'string', + 'required' => true, + 'description' => 'Cloudflare API Token with Zone:Edit permissions', + 'sensitive' => true, + ], + 'zone_id' => [ + 'type' => 'string', + 'required' => false, + 'description' => 'Cloudflare Zone ID (optional - will auto-discover if not provided)', + 'sensitive' => false, + ], + ]; + } + + /** + * Validate the provider configuration. + */ + public function validateConfiguration(array $config): bool + { + if (empty($config['api_token'])) { + throw DnsProviderException::invalidConfiguration('cloudflare', 'api_token'); + } + + return true; + } + + /** + * Get the supported record types for Cloudflare. + */ + public function getSupportedRecordTypes(): array + { + return ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'SRV', 'NS', 'PTR', 'CAA']; + } + + /** + * Get the zone ID for a domain. + */ + private function getZoneId(string $domain): string + { + // Use provided zone_id if available + if (!empty($this->config['zone_id'])) { + return $this->config['zone_id']; + } + + // Fall back to auto-discovery + try { + $response = $this->client->get('zones', [ + 'query' => ['name' => $domain] + ]); + + $data = json_decode($response->getBody()->getContents(), true); + + if (!$data['success'] || empty($data['result'])) { + throw new \Exception("Domain zone not found or inaccessible."); + } + + return $data['result'][0]['id']; + } catch (GuzzleException $e) { + throw DnsProviderException::connectionFailed('cloudflare', 'DNS service temporarily unavailable.'); + } + } +} diff --git a/app/Services/Nodes/NodeCreationService.php b/app/Services/Nodes/NodeCreationService.php index b21589c4a..75246cec0 100644 --- a/app/Services/Nodes/NodeCreationService.php +++ b/app/Services/Nodes/NodeCreationService.php @@ -28,6 +28,11 @@ class NodeCreationService $data['daemon_token'] = app(Encrypter::class)->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); $data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH); + // Automatically set use_separate_fqdns based on whether internal_fqdn is provided + if (isset($data['internal_fqdn'])) { + $data['use_separate_fqdns'] = !empty($data['internal_fqdn']); + } + return $this->repository->create($data, true, true); } } diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php index 11078af53..30f206b96 100644 --- a/app/Services/Nodes/NodeUpdateService.php +++ b/app/Services/Nodes/NodeUpdateService.php @@ -37,6 +37,11 @@ class NodeUpdateService $data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH); } + // Automatically set use_separate_fqdns based on whether internal_fqdn is provided + if (isset($data['internal_fqdn'])) { + $data['use_separate_fqdns'] = !empty($data['internal_fqdn']); + } + [$updated, $exception] = $this->connection->transaction(function () use ($data, $node) { /** @var Node $updated */ $updated = $this->repository->withFreshModel()->update($node->id, $data, true, true); @@ -51,7 +56,17 @@ class NodeUpdateService // node doesn't actually care about this. // // @see https://github.com/pterodactyl/panel/issues/1931 - $node->fqdn = $updated->fqdn; + + // Update the node's internal FQDN fields if they were changed + if (isset($data['fqdn'])) { + $node->fqdn = $updated->fqdn; + } + if (isset($data['internal_fqdn'])) { + $node->internal_fqdn = $updated->internal_fqdn; + } + if (isset($data['use_separate_fqdns'])) { + $node->use_separate_fqdns = $updated->use_separate_fqdns; + } $this->configurationRepository->setNode($node)->update($updated); } catch (DaemonConnectionException $exception) { diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php index 56b708499..3c6baceb9 100644 --- a/app/Services/Schedules/ProcessScheduleService.php +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -13,76 +13,76 @@ use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class ProcessScheduleService { - /** - * ProcessScheduleService constructor. - */ - public function __construct(private ConnectionInterface $connection, private Dispatcher $dispatcher, private DaemonServerRepository $serverRepository) - { + /** + * ProcessScheduleService constructor. + */ + public function __construct(private ConnectionInterface $connection, private Dispatcher $dispatcher, private DaemonServerRepository $serverRepository) + { + } + + /** + * Process a schedule and push the first task onto the queue worker. + * + * @throws \Throwable + */ + public function handle(Schedule $schedule, bool $now = false): void + { + /** @var \Pterodactyl\Models\Task $task */ + $task = $schedule->tasks()->orderBy('sequence_id')->first(); + + if (is_null($task)) { + throw new DisplayException('Cannot process schedule for task execution: no tasks are registered.'); } - /** - * Process a schedule and push the first task onto the queue worker. - * - * @throws \Throwable - */ - public function handle(Schedule $schedule, bool $now = false): void - { - /** @var \Pterodactyl\Models\Task $task */ - $task = $schedule->tasks()->orderBy('sequence_id')->first(); + $this->connection->transaction(function () use ($schedule, $task) { + $schedule->forceFill([ + 'is_processing' => true, + 'next_run_at' => $schedule->getNextRunDate(), + ])->saveOrFail(); - if (is_null($task)) { - throw new DisplayException('Cannot process schedule for task execution: no tasks are registered.'); + $task->update(['is_queued' => true]); + }); + + $job = new RunTaskJob($task, $now); + if ($schedule->only_when_online) { + // Check that the server is currently in a starting or running state before executing + // this schedule if this option has been set. + try { + $details = $this->serverRepository->setServer($schedule->server)->getDetails(); + $state = $details['state'] ?? 'offline'; + // If the server is stopping or offline just do nothing with this task. + if (in_array($state, ['offline', 'stopping'])) { + $job->failed(); + + return; } - - $this->connection->transaction(function () use ($schedule, $task) { - $schedule->forceFill([ - 'is_processing' => true, - 'next_run_at' => $schedule->getNextRunDate(), - ])->saveOrFail(); - - $task->update(['is_queued' => true]); - }); - - $job = new RunTaskJob($task, $now); - if ($schedule->only_when_online) { - // Check that the server is currently in a starting or running state before executing - // this schedule if this option has been set. - try { - $details = $this->serverRepository->setServer($schedule->server)->getDetails(); - $state = $details['state'] ?? 'offline'; - // If the server is stopping or offline just do nothing with this task. - if (in_array($state, ['offline', 'stopping'])) { - $job->failed(); - - return; - } - } catch (\Exception $exception) { - if (!$exception instanceof DaemonConnectionException) { - // If we encountered some exception during this process that wasn't just an - // issue connecting to Wings run the failed sequence for a job. Otherwise we - // can just quietly mark the task as completed without actually running anything. - $job->failed($exception); - } - $job->failed(); - - return; - } + } catch (\Exception $exception) { + if (!$exception instanceof DaemonConnectionException) { + // If we encountered some exception during this process that wasn't just an + // issue connecting to Wings run the failed sequence for a job. Otherwise we + // can just quietly mark the task as completed without actually running anything. + $job->failed($exception); } + $job->failed(); - if (!$now) { - $this->dispatcher->dispatch($job->delay($task->time_offset)); - } else { - // When using dispatchNow the RunTaskJob::failed() function is not called automatically - // so we need to manually trigger it and then continue with the exception throw. - // - // @see https://github.com/pterodactyl/panel/issues/2550 - try { - $this->dispatcher->dispatchNow($job); - } catch (\Exception $exception) { - $job->failed($exception); - - throw $exception; - } - } + return; + } } + + if (!$now) { + $this->dispatcher->dispatch($job->delay($task->time_offset)); + } else { + // When using dispatchNow the RunTaskJob::failed() function is not called automatically + // so we need to manually trigger it and then continue with the exception throw. + // + // @see https://github.com/pterodactyl/panel/issues/2550 + try { + $this->dispatcher->dispatchNow($job); + } catch (\Exception $exception) { + $job->failed($exception); + + throw $exception; + } + } + } } diff --git a/app/Services/ServerOperations/EggChangeService.php b/app/Services/ServerOperations/EggChangeService.php new file mode 100644 index 000000000..d1c0359f2 --- /dev/null +++ b/app/Services/ServerOperations/EggChangeService.php @@ -0,0 +1,236 @@ +validationService->validateServerState($server); + + $egg = Egg::query() + ->with(['variables', 'nest']) + ->findOrFail($eggId); + + if ($egg->nest_id !== $nestId) { + throw new BadRequestHttpException('The specified egg does not belong to the specified nest.'); + } + + $variables = $egg->variables()->orderBy('name')->get(); + $dockerImages = $egg->docker_images ?? []; + + // Check subdomain compatibility + $subdomainWarning = $this->checkSubdomainCompatibility($server, $egg); + + $result = [ + 'egg' => [ + 'id' => $egg->id, + 'name' => e($egg->name), + 'description' => e($egg->description), + 'startup' => $egg->startup, + ], + 'variables' => $variables->map(function ($variable) { + return [ + 'id' => $variable->id, + 'name' => e($variable->name), + 'description' => e($variable->description), + 'env_variable' => $variable->env_variable, + 'default_value' => $variable->default_value, + 'user_viewable' => $variable->user_viewable, + 'user_editable' => $variable->user_editable, + 'rules' => $variable->rules, + ]; + }), + 'docker_images' => $dockerImages, + 'default_docker_image' => !empty($dockerImages) ? array_keys($dockerImages)[0] : null, + ]; + + // Add subdomain warning if applicable + if ($subdomainWarning) { + $result['warnings'] = [$subdomainWarning]; + } + + return $result; + } + + /** + * Validate egg change parameters. + */ + public function validateEggChangeParameters( + Server $server, + int $eggId, + int $nestId, + ?string $dockerImage = null, + ?string $startupCommand = null + ): array { + $this->validationService->validateCanAcceptOperation($server, 'egg_change'); + + $egg = Egg::query() + ->with(['variables', 'nest']) + ->findOrFail($eggId); + + if ($egg->nest_id !== $nestId) { + throw new BadRequestHttpException('The specified egg does not belong to the specified nest.'); + } + + $startupCommand = $startupCommand ? trim($startupCommand) : null; + $dockerImage = $dockerImage ? trim($dockerImage) : null; + + if ($startupCommand && strlen($startupCommand) > 2048) { + throw new BadRequestHttpException('Startup command is too long (max 2048 characters).'); + } + + if ($dockerImage) { + $allowedImages = array_values($egg->docker_images ?? []); + if (!empty($allowedImages) && !in_array($dockerImage, $allowedImages)) { + throw new BadRequestHttpException('The specified Docker image is not allowed for this egg.'); + } + } + + if (!$dockerImage && !empty($egg->docker_images)) { + $dockerImage = array_values($egg->docker_images)[0]; + } + + return [ + 'egg' => $egg, + 'docker_image' => $dockerImage, + 'startup_command' => $startupCommand, + ]; + } + + /** + * Apply egg change asynchronously. + */ + public function applyEggChangeAsync( + Server $server, + User $user, + int $eggId, + int $nestId, + ?string $dockerImage = null, + ?string $startupCommand = null, + array $environment = [], + bool $shouldBackup = false, + bool $shouldWipe = false + ): array { + $validated = $this->validateEggChangeParameters( + $server, + $eggId, + $nestId, + $dockerImage, + $startupCommand + ); + + $dockerImage = $validated['docker_image']; + $startupCommand = $validated['startup_command']; + + $operation = $this->operationService->createOperation( + $server, + $user, + 'egg_change', + [ + 'from_egg_id' => $server->egg_id, + 'to_egg_id' => $eggId, + 'from_nest_id' => $server->nest_id, + 'to_nest_id' => $nestId, + 'docker_image' => $dockerImage, + 'startup_command' => $startupCommand, + 'environment' => $environment, + 'should_backup' => $shouldBackup, + 'should_wipe' => $shouldWipe, + ] + ); + + try { + ApplyEggChangeJob::dispatch( + $server, + $user, + $eggId, + $nestId, + $dockerImage, + $startupCommand, + $environment, + $shouldBackup, + $shouldWipe, + $operation->operation_id + ); + } catch (Exception $e) { + $operation->delete(); + + Log::error('Failed to dispatch egg change job', [ + 'server_id' => $server->id, + 'operation_id' => $operation->operation_id, + 'error' => $e->getMessage(), + ]); + + throw new \RuntimeException('Failed to queue egg change operation. Please try again.'); + } + + return [ + 'message' => 'Egg change operation has been queued for processing.', + 'operation_id' => $operation->operation_id, + 'status' => 'pending', + ]; + } + + /** + * Check if changing to the new egg will affect subdomain compatibility. + */ + private function checkSubdomainCompatibility(Server $server, Egg $newEgg): ?array + { + // Check if server currently has an active subdomain + $activeSubdomain = $server->activeSubdomain; + + if (!$activeSubdomain) { + return null; // No subdomain to worry about + } + + // Check if the current egg supports subdomains + $currentSupportsSubdomain = $server->supportsSubdomains(); + + if (!$currentSupportsSubdomain) { + return null; // Current egg doesn't support subdomains anyway + } + + // Create a temporary server instance with the new egg to test compatibility + $tempServer = clone $server; + $tempServer->egg = $newEgg; + $tempServer->egg_id = $newEgg->id; + + // Check if the new egg supports subdomains + $newSupportsSubdomain = $tempServer->supportsSubdomains(); + + if (!$newSupportsSubdomain) { + return [ + 'type' => 'subdomain_incompatible', + 'message' => "Warning: The new egg does not support subdomains. Your current subdomain ({$activeSubdomain->full_domain}) will be deleted when you apply this change.", + 'severity' => 'warning', + ]; + } + + return null; // New egg supports subdomains, no warning needed + } +} \ No newline at end of file diff --git a/app/Services/ServerOperations/ServerOperationService.php b/app/Services/ServerOperations/ServerOperationService.php new file mode 100644 index 000000000..3ccfdfbaf --- /dev/null +++ b/app/Services/ServerOperations/ServerOperationService.php @@ -0,0 +1,172 @@ +active()->count(); + return $activeOperations === 0; + } catch (Exception $e) { + Log::warning('Failed to check server operation capacity', [ + 'server_id' => $server->id, + 'error' => $e->getMessage(), + ]); + + return true; + } + } + + /** + * Create a new server operation. + */ + public function createOperation( + Server $server, + User $user, + string $type, + array $parameters = [], + ?string $message = null + ): ServerOperation { + if (!$this->canAcceptOperation($server)) { + throw new ConflictHttpException('Server cannot accept new operations at this time.'); + } + + $operationId = Str::uuid()->toString(); + + return ServerOperation::create([ + 'operation_id' => $operationId, + 'server_id' => $server->id, + 'user_id' => $user->id, + 'type' => $type, + 'status' => ServerOperation::STATUS_PENDING, + 'message' => $message ?? 'Operation queued for processing...', + 'parameters' => $parameters, + ]); + } + + /** + * Get operation by ID for server. + */ + public function getOperation(Server $server, string $operationId): ServerOperation + { + $operation = ServerOperation::where('operation_id', $operationId) + ->where('server_id', $server->id) + ->firstOrFail(); + + if ($operation->hasTimedOut()) { + $operation->markAsFailed('Operation timed out'); + } + + return $operation; + } + + /** + * Get recent operations for server. + */ + public function getServerOperations(Server $server, int $limit = 20): array + { + $this->updateTimedOutOperations($server); + + $operations = ServerOperation::forServer($server) + ->orderBy('created_at', 'desc') + ->limit($limit) + ->get(); + + return $operations->map(function ($operation) { + return $this->formatOperationResponse($operation); + })->toArray(); + } + + /** + * Update timed out operations for a server. + */ + public function updateTimedOutOperations(Server $server): int + { + try { + $timedOutOperations = ServerOperation::forServer($server)->timedOut()->get(); + + foreach ($timedOutOperations as $operation) { + $operation->markAsFailed('Operation timed out'); + } + + return $timedOutOperations->count(); + } catch (Exception $e) { + Log::warning('Failed to update timed out operations', [ + 'server_id' => $server->id, + 'error' => $e->getMessage(), + ]); + + return 0; + } + } + + /** + * Format operation for API response. + */ + public function formatOperationResponse(ServerOperation $operation): array + { + return [ + 'operation_id' => $operation->operation_id, + 'type' => $operation->type, + 'status' => $operation->status, + 'message' => $operation->message, + 'created_at' => $operation->created_at->toDateTimeString(), + 'updated_at' => $operation->updated_at->toDateTimeString(), + 'started_at' => $operation->started_at?->toDateTimeString(), + 'parameters' => $operation->parameters, + 'is_active' => $operation->isActive(), + 'is_completed' => $operation->isCompleted(), + 'has_failed' => $operation->hasFailed(), + 'has_timed_out' => $operation->hasTimedOut(), + ]; + } + + /** + * Clean up old completed operations. + */ + public function cleanupOldOperations(int $daysOld = null): int + { + $daysOld = $daysOld ?? config('server_operations.cleanup.retain_days', 30); + $chunkSize = config('server_operations.cleanup.chunk_size', 100); + + try { + $deletedCount = 0; + + ServerOperation::forCleanup($daysOld) + ->chunk($chunkSize, function ($operations) use (&$deletedCount) { + foreach ($operations as $operation) { + $operation->delete(); + $deletedCount++; + } + }); + + return $deletedCount; + } catch (Exception $e) { + Log::error('Failed to cleanup old server operations', [ + 'error' => $e->getMessage(), + 'days_old' => $daysOld, + ]); + + return 0; + } + } +} \ No newline at end of file diff --git a/app/Services/ServerOperations/ServerStateValidationService.php b/app/Services/ServerOperations/ServerStateValidationService.php new file mode 100644 index 000000000..837b5b22c --- /dev/null +++ b/app/Services/ServerOperations/ServerStateValidationService.php @@ -0,0 +1,73 @@ +status === Server::STATUS_INSTALLING) { + throw new ConflictHttpException('Server is currently being installed and cannot be modified.'); + } + + if ($server->status === Server::STATUS_SUSPENDED) { + throw new ConflictHttpException('Server is suspended and cannot be modified.'); + } + + if ($server->transfer) { + throw new ConflictHttpException('Server is currently being transferred and cannot be modified.'); + } + + $server->refresh(); + } catch (\Exception $e) { + Log::error('Failed to validate server state', [ + 'server_id' => $server->id, + 'error' => $e->getMessage(), + ]); + + if ($e instanceof ConflictHttpException) { + throw $e; + } + + Log::warning('Server state validation failed, allowing request to proceed', [ + 'server_id' => $server->id, + 'error' => $e->getMessage(), + ]); + } + } + + /** + * Check for active operations on server. + */ + public function checkForActiveOperations(Server $server): void + { + $activeOperation = ServerOperation::forServer($server)->active()->first(); + if ($activeOperation) { + throw new ConflictHttpException('Another operation is currently in progress for this server. Please wait for it to complete.'); + } + } + + /** + * Validate server can accept the operation. + */ + public function validateCanAcceptOperation(Server $server, string $operationType): void + { + $this->validateServerState($server); + $this->checkForActiveOperations($server); + } +} \ No newline at end of file diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index a977f8381..cd145c489 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -46,7 +46,7 @@ class BuildModificationService // If any of these values are passed through in the data array go ahead and set // them correctly on the server model. - $merge = Arr::only($data, ['oom_disabled', 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', 'allocation_id']); + $merge = Arr::only($data, ['oom_disabled', 'exclude_from_resource_calculation', 'memory', 'overhead_memory', 'swap', 'io', 'cpu', 'threads', 'disk', 'allocation_id']); $server->forceFill(array_merge($merge, [ 'database_limit' => Arr::get($data, 'database_limit', 0) ?? null, diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index 72f327796..5be55ba66 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -10,8 +10,10 @@ class ServerConfigurationStructureService /** * ServerConfigurationStructureService constructor. */ - public function __construct(private EnvironmentService $environment) - { + public function __construct( + private EnvironmentService $environment, + private StartupCommandService $startupCommandService + ) { } /** @@ -50,10 +52,10 @@ class ServerConfigurationStructureService ], 'suspended' => $server->isSuspended(), 'environment' => $this->environment->handle($server), - 'invocation' => $server->startup, + 'invocation' => $this->startupCommandService->handle($server), 'skip_egg_scripts' => $server->skip_scripts, 'build' => [ - 'memory_limit' => $server->memory, + 'memory_limit' => $server->memory + $server->overhead_memory, 'swap' => $server->swap, 'io_weight' => $server->io, 'cpu_limit' => $server->cpu, @@ -112,6 +114,7 @@ class ServerConfigurationStructureService 'env' => $this->environment->handle($server), 'oom_disabled' => $server->oom_disabled, 'memory' => (int) $server->memory, + 'overhead_memory' => (int) $server->overhead_memory, 'swap' => (int) $server->swap, 'io' => (int) $server->io, 'cpu' => (int) $server->cpu, diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 6f900b12d..d298e8e54 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -148,12 +148,14 @@ class ServerCreationService 'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']), 'owner_id' => Arr::get($data, 'owner_id'), 'memory' => Arr::get($data, 'memory'), + 'overhead_memory' => Arr::get($data, 'overhead_memory', 0), 'swap' => Arr::get($data, 'swap'), 'disk' => Arr::get($data, 'disk'), 'io' => Arr::get($data, 'io'), 'cpu' => Arr::get($data, 'cpu'), 'threads' => Arr::get($data, 'threads'), 'oom_disabled' => Arr::get($data, 'oom_disabled') ?? true, + 'exclude_from_resource_calculation' => Arr::get($data, 'exclude_from_resource_calculation') ?? false, 'allocation_id' => Arr::get($data, 'allocation_id'), 'nest_id' => Arr::get($data, 'nest_id'), 'egg_id' => Arr::get($data, 'egg_id'), diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php index 714e22623..01d463094 100644 --- a/app/Services/Servers/ServerDeletionService.php +++ b/app/Services/Servers/ServerDeletionService.php @@ -8,7 +8,9 @@ use Illuminate\Support\Facades\Log; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Services\Databases\DatabaseManagementService; +use Pterodactyl\Services\Backups\DeleteBackupService; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Exceptions\Service\Backup\BackupLockedException; class ServerDeletionService { @@ -21,6 +23,7 @@ class ServerDeletionService private ConnectionInterface $connection, private DaemonServerRepository $daemonServerRepository, private DatabaseManagementService $databaseManagementService, + private DeleteBackupService $deleteBackupService, ) { } @@ -57,6 +60,50 @@ class ServerDeletionService } $this->connection->transaction(function () use ($server) { + // Delete all backups associated with this server + foreach ($server->backups as $backup) { + try { + $this->deleteBackupService->handle($backup); + } catch (BackupLockedException $exception) { + // If the backup is locked, unlock it and try again since we're deleting the entire server + $backup->update(['is_locked' => false]); + + try { + $this->deleteBackupService->handle($backup); + } catch (\Exception $retryException) { + if (!$this->force) { + throw $retryException; + } + + // If we still can't delete the backup from storage, at least remove the database record + // to prevent orphaned backup entries + $backup->delete(); + + Log::warning('Failed to delete unlocked backup during server deletion', [ + 'backup_id' => $backup->id, + 'backup_uuid' => $backup->uuid, + 'server_id' => $server->id, + 'exception' => $retryException->getMessage(), + ]); + } + } catch (\Exception $exception) { + if (!$this->force) { + throw $exception; + } + + // If we can't delete the backup from storage, at least remove the database record + // to prevent orphaned backup entries + $backup->delete(); + + Log::warning('Failed to delete backup during server deletion', [ + 'backup_id' => $backup->id, + 'backup_uuid' => $backup->uuid, + 'server_id' => $server->id, + 'exception' => $exception->getMessage(), + ]); + } + } + foreach ($server->databases as $database) { try { $this->databaseManagementService->delete($database); diff --git a/app/Services/Servers/StartupCommandService.php b/app/Services/Servers/StartupCommandService.php index efdbbc5c4..34340ac3d 100644 --- a/app/Services/Servers/StartupCommandService.php +++ b/app/Services/Servers/StartupCommandService.php @@ -11,8 +11,23 @@ class StartupCommandService */ public function handle(Server $server, bool $hideAllValues = false): string { - $find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}']; - $replace = [$server->memory, $server->allocation->ip, $server->allocation->port]; + $find = [ + '{{SERVER_MEMORY}}', + '{{SERVER_IP}}', + '{{SERVER_PORT}}', + '{{SERVER_UUID}}', + '{{SERVER_NAME}}', + '{{SERVER_CPU}}' + ]; + + $replace = [ + $server->memory, + $server->allocation->ip, + $server->allocation->port, + $server->uuid, + $server->name, + $server->cpu + ]; foreach ($server->variables as $variable) { $find[] = '{{' . $variable->env_variable . '}}'; diff --git a/app/Services/Servers/StartupCommandUpdateService.php b/app/Services/Servers/StartupCommandUpdateService.php new file mode 100644 index 000000000..79d6a8f1a --- /dev/null +++ b/app/Services/Servers/StartupCommandUpdateService.php @@ -0,0 +1,46 @@ +startup; + + return $this->connection->transaction(function () use ($server, $startup, $original) { + $server->update(['startup' => $startup]); + + // Log the activity + Activity::event('server:startup.command') + ->subject($server) + ->property([ + 'old' => $original, + 'new' => $startup, + ]) + ->log(); + + // Sync the server configuration with Wings daemon + $this->daemonServerRepository->setServer($server)->sync(); + + return $server->refresh(); + }); + } +} \ No newline at end of file diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php index 676a84aba..e4d94bb41 100644 --- a/app/Services/Servers/VariableValidatorService.php +++ b/app/Services/Servers/VariableValidatorService.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Services\Servers; +use Illuminate\Support\Arr; use Pterodactyl\Models\User; use Illuminate\Support\Collection; use Pterodactyl\Models\EggVariable; @@ -39,8 +40,15 @@ class VariableValidatorService $data = $rules = $customAttributes = []; foreach ($variables as $variable) { - $data['environment'][$variable->env_variable] = array_get($fields, $variable->env_variable); - $rules['environment.' . $variable->env_variable] = $variable->rules; + $value = Arr::get($fields, $variable->env_variable); + $data['environment'][$variable->env_variable] = $value; + + // Make rules nullable to handle empty environment variables, but don't duplicate if already nullable + $rules_string = $variable->rules; + if (!str_starts_with($rules_string, 'nullable')) { + $rules_string = 'nullable|' . $rules_string; + } + $rules['environment.' . $variable->env_variable] = $rules_string; $customAttributes['environment.' . $variable->env_variable] = trans('validation.internal.variable_value', ['env' => $variable->name]); } diff --git a/app/Services/Subdomain/Features/FactorioSubdomainFeature.php b/app/Services/Subdomain/Features/FactorioSubdomainFeature.php new file mode 100644 index 000000000..36bac69e6 --- /dev/null +++ b/app/Services/Subdomain/Features/FactorioSubdomainFeature.php @@ -0,0 +1,57 @@ +allocation->ip; + $port = $server->allocation->port; + $fullDomain = $subdomain . '.' . $domain; + + $records = []; + + // A record pointing to the server IP + $records[] = [ + 'name' => $subdomain, + 'type' => 'A', + 'content' => $ip, + 'ttl' => 300, + ]; + + // SRV record for Factorio (_factorio._udp) + $records[] = [ + 'name' => '_factorio._udp.' . $subdomain, + 'type' => 'SRV', + 'content' => [ + 'service' => '_factorio', + 'proto' => '_udp', + 'name' => '_factorio._udp.' . $subdomain, + 'priority' => 0, + 'weight' => 0, + 'port' => $port, + 'target' => $fullDomain, + 'content' => "SRV 0 0 {$port} {$fullDomain}", + ], + 'ttl' => 300, + ]; + + return $records; + } + +} \ No newline at end of file diff --git a/app/Services/Subdomain/Features/MinecraftSubdomainFeature.php b/app/Services/Subdomain/Features/MinecraftSubdomainFeature.php new file mode 100644 index 000000000..49938d5a4 --- /dev/null +++ b/app/Services/Subdomain/Features/MinecraftSubdomainFeature.php @@ -0,0 +1,59 @@ +allocation->ip; + $port = $server->allocation->port; + $fullDomain = $subdomain . '.' . $domain; + + $records = []; + + // A record pointing to the server IP + $records[] = [ + 'name' => $subdomain, + 'type' => 'A', + 'content' => $ip, + 'ttl' => 300, + ]; + + // SRV record for Minecraft (only if not using default port) + if ($port != 25565) { + $records[] = [ + 'name' => '_minecraft._tcp.' . $subdomain, + 'type' => 'SRV', + 'content' => [ + 'service' => '_minecraft', + 'proto' => '_tcp', + 'name' => $subdomain, + 'priority' => 0, + 'weight' => 5, + 'port' => $port, + 'target' => $fullDomain, + 'content' => "SRV 0 5 {$port} {$fullDomain}", + ], + 'ttl' => 300, + ]; + } + + return $records; + } + +} \ No newline at end of file diff --git a/app/Services/Subdomain/Features/RustSubdomainFeature.php b/app/Services/Subdomain/Features/RustSubdomainFeature.php new file mode 100644 index 000000000..4b57c9eed --- /dev/null +++ b/app/Services/Subdomain/Features/RustSubdomainFeature.php @@ -0,0 +1,57 @@ +allocation->ip; + $port = $server->allocation->port; + $fullDomain = $subdomain . '.' . $domain; + + $records = []; + + // A record pointing to the server IP + $records[] = [ + 'name' => $subdomain, + 'type' => 'A', + 'content' => $ip, + 'ttl' => 300, + ]; + + // SRV record for Rust (_rust._udp) + $records[] = [ + 'name' => '_rust._udp.' . $subdomain, + 'type' => 'SRV', + 'content' => [ + 'service' => '_rust', + 'proto' => '_udp', + 'name' => '_rust._udp.' . $subdomain, + 'priority' => 0, + 'weight' => 5, + 'port' => $port, + 'target' => $fullDomain, + 'content' => "SRV 0 5 {$port} {$fullDomain}", + ], + 'ttl' => 300, + ]; + + return $records; + } + +} \ No newline at end of file diff --git a/app/Services/Subdomain/Features/TeamSpeakSubdomainFeature.php b/app/Services/Subdomain/Features/TeamSpeakSubdomainFeature.php new file mode 100644 index 000000000..cdf83c528 --- /dev/null +++ b/app/Services/Subdomain/Features/TeamSpeakSubdomainFeature.php @@ -0,0 +1,89 @@ +allocation->ip; + $port = $server->allocation->port; + $fullDomain = $subdomain . '.' . $domain; + + $records = []; + + // A record pointing to the server IP (for fallback and TSDNS) + $records[] = [ + 'name' => $subdomain, + 'type' => 'A', + 'content' => $ip, + 'ttl' => 300, + ]; + + // Primary TeamSpeak 3 SRV record (_ts3._udp) + $records[] = [ + 'name' => '_ts3._udp.' . $subdomain, + 'type' => 'SRV', + 'content' => [ + 'service' => '_ts3', + 'proto' => '_udp', + 'name' => '_ts3._udp.' . $subdomain, + 'priority' => 0, + 'weight' => 5, + 'port' => $port, + 'target' => $fullDomain, + 'content' => "SRV 0 5 {$port} {$fullDomain}", + ], + 'ttl' => 300, + ]; + + // Optional TSDNS SRV record if running on a common TSDNS port + // This allows for TSDNS redirection if needed + if ($this->shouldCreateTsdnsRecord($port)) { + $records[] = [ + 'name' => '_tsdns._tcp.' . $subdomain, + 'type' => 'SRV', + 'content' => [ + 'service' => '_tsdns', + 'proto' => '_tcp', + 'name' => '_tsdns._tcp.' . $subdomain, + 'priority' => 0, + 'weight' => 5, + 'port' => $port, + 'target' => $fullDomain, + 'content' => "SRV 0 5 {$port} {$fullDomain}", + ], + 'ttl' => 300, + ]; + } + + return $records; + } + + /** + * Determine if we should create a TSDNS SRV record. + * This is typically for common TSDNS ports or when configured. + */ + private function shouldCreateTsdnsRecord(int $port): bool + { + // Common TSDNS ports + $tsdnsPorts = [41144, 41145, 41146, 41147, 41148]; + + // Create TSDNS record for common TSDNS ports or if the port looks like it might be TSDNS + return in_array($port, $tsdnsPorts) || ($port >= 41144 && $port <= 41200); + } +} \ No newline at end of file diff --git a/app/Services/Subdomain/SubdomainGeneratorService.php b/app/Services/Subdomain/SubdomainGeneratorService.php new file mode 100644 index 000000000..4969ab496 --- /dev/null +++ b/app/Services/Subdomain/SubdomainGeneratorService.php @@ -0,0 +1,342 @@ +adjectives[array_rand($this->adjectives)]; + $animal = $this->animals[array_rand($this->animals)]; + $number = mt_rand(100, 999); + + // Ensure DNS-safe format + return Str::slug(sprintf("%s-%s%d", $adjective, $animal, $number)); + } + + /** + * Generate a unique subdomain not present in $existingSubdomains. + * Tries up to $maxAttempts times using the base pool (~45M possibilities). + * If all attempts collide, fall back to a larger space by appending a 5-char random suffix. + */ + public function generateUnique( + array $existingSubdomains, + int $maxAttempts = 50 + ): string { + $attempts = 0; + + while ($attempts < $maxAttempts) { + $subdomain = $this->generate(); + + if (!in_array($subdomain, $existingSubdomains, true)) { + return $subdomain; + } + + $attempts++; + } + + // Fallback: ensure uniqueness with much larger pool. + // Str::random(5) → [a-z0-9], i.e. 36^5 = 60,466,176 suffix possibilities. + // With ~200 adjectives × ~250 animals, fallback space is: + // 200 × 250 × 60,466,176 = 3,023,308,800,000 (~3 trillion). + + // So basically, if we ever hit this fallback, we are almost certainly + // filthy rich or extremely unlucky. Either way, we win. - ellie + return Str::slug( + sprintf( + "%s-%s-%s", + $this->adjectives[array_rand($this->adjectives)], + $this->animals[array_rand($this->animals)], + strtolower(Str::random(5)) + ) + ); + } +} diff --git a/app/Services/Subdomain/SubdomainManagementService.php b/app/Services/Subdomain/SubdomainManagementService.php new file mode 100644 index 000000000..957f62107 --- /dev/null +++ b/app/Services/Subdomain/SubdomainManagementService.php @@ -0,0 +1,534 @@ +dnsProviders = [ + 'cloudflare' => CloudflareProvider::class, + ]; + + // Register subdomain features + $this->subdomainFeatures = [ + 'subdomain_factorio' => FactorioSubdomainFeature::class, + 'subdomain_minecraft' => MinecraftSubdomainFeature::class, + 'subdomain_rust' => RustSubdomainFeature::class, + 'subdomain_teamspeak' => TeamSpeakSubdomainFeature::class, + ]; + } + + /** + * Create a subdomain for a server + * + * @param Server $server + * @param Domain $domain + * @param string $subdomain + * @return ServerSubdomain + * @throws \Exception + */ + public function createSubdomain(Server $server, Domain $domain, string $subdomain): ServerSubdomain + { + // Check if server supports subdomains + $feature = $this->getServerSubdomainFeature($server); + if (!$feature) { + throw new \Exception('Server does not support subdomains.'); + } + + // Validate subdomain + $this->validateSubdomain($subdomain, $feature, $domain); + + // Get DNS provider + try { + $dnsProvider = $this->getDnsProvider($domain); + } catch (\Exception $e) { + throw new \Exception('DNS service temporarily unavailable.'); + } + + // Get DNS records to create + $dnsRecords = $feature->getDnsRecords($server, $subdomain, $domain->name); + + // Normalize IP addresses in DNS records + $dnsRecords = $this->normalizeIpAddresses($dnsRecords); + + // Use database transaction for consistency + return DB::transaction(function () use ($server, $domain, $subdomain, $feature, $dnsProvider, $dnsRecords) { + // CRITICAL: Check if server already has an active subdomain (prevents race conditions) + $existingServerSubdomain = ServerSubdomain::where('server_id', $server->id) + ->where('is_active', true) + ->lockForUpdate() // Use row-level locking to prevent race conditions + ->first(); + + if ($existingServerSubdomain) { + throw new \Exception('Server already has an active subdomain. Please delete it first.'); + } + + // Double-check subdomain availability within transaction + $existing = ServerSubdomain::where('domain_id', $domain->id) + ->where('subdomain', $subdomain) + ->where('is_active', true) + ->first(); + + if ($existing) { + throw new \Exception('Subdomain is not available.'); + } + + // Create DNS records first + $createdRecordIds = []; + $rollbackRequired = false; + + try { + foreach ($dnsRecords as $record) { + $recordId = $dnsProvider->createRecord( + $domain->name, + $record['name'], + $record['type'], + $record['content'], + $record['ttl'] ?? 300 + ); + $createdRecordIds[] = $recordId; + } + + // Create subdomain record in database + $serverSubdomain = ServerSubdomain::create([ + 'server_id' => $server->id, + 'domain_id' => $domain->id, + 'subdomain' => $subdomain, + 'record_type' => str_replace('subdomain_', '', $feature->getFeatureName()), + 'dns_records' => $createdRecordIds, + 'is_active' => true, + ]); + + return $serverSubdomain; + } catch (\Exception $e) { + $rollbackRequired = true; + throw $e; + } finally { + // Clean up DNS records if transaction failed + if ($rollbackRequired && !empty($createdRecordIds)) { + $this->cleanupDnsRecords($dnsProvider, $domain->name, $createdRecordIds); + } + } + }); + } + + /** + * Update a subdomain's DNS records + * + * @param ServerSubdomain $serverSubdomain + * @return void + * @throws \Exception + */ + public function updateSubdomain(ServerSubdomain $serverSubdomain): void + { + $server = $serverSubdomain->server; + $domain = $serverSubdomain->domain; + + $feature = $this->getServerSubdomainFeature($server); + if (!$feature) { + throw new \Exception('Server no longer supports subdomains.'); + } + + try { + $dnsProvider = $this->getDnsProvider($domain); + } catch (\Exception $e) { + throw new \Exception('DNS service temporarily unavailable.'); + } + + $newDnsRecords = $feature->getDnsRecords($server, $serverSubdomain->subdomain, $domain->name); + $newDnsRecords = $this->normalizeIpAddresses($newDnsRecords); + + DB::transaction(function () use ($serverSubdomain, $dnsProvider, $domain, $newDnsRecords) { + $recordIds = $serverSubdomain->dns_records; + $updatedRecordIds = []; + $rollbackData = []; + + try { + // Update/create records + foreach ($newDnsRecords as $index => $record) { + if (isset($recordIds[$index])) { + // Store original state for potential rollback + try { + $originalRecord = $dnsProvider->getRecord($domain->name, $recordIds[$index]); + $rollbackData[$recordIds[$index]] = $originalRecord; + } catch (\Exception $e) { + // If we can't get original record, we can't rollback safely + Log::warning("Cannot retrieve original DNS record for rollback: {$recordIds[$index]}"); + } + + // Update existing record + $dnsProvider->updateRecord( + $domain->name, + $recordIds[$index], + $record['content'], + $record['ttl'] ?? null + ); + $updatedRecordIds[] = $recordIds[$index]; + } else { + // Create new record if needed + $recordId = $dnsProvider->createRecord( + $domain->name, + $record['name'], + $record['type'], + $record['content'], + $record['ttl'] ?? 300 + ); + $updatedRecordIds[] = $recordId; + } + } + + // Delete any extra records + $recordsToDelete = array_slice($recordIds, count($newDnsRecords)); + foreach ($recordsToDelete as $recordId) { + $dnsProvider->deleteRecord($domain->name, $recordId); + } + + // Update database record + $serverSubdomain->update([ + 'dns_records' => $updatedRecordIds, + ]); + } catch (\Exception $e) { + // Attempt rollback of DNS changes + $this->rollbackDnsChanges($dnsProvider, $domain->name, $rollbackData, $updatedRecordIds); + throw new \Exception('Failed to update subdomain DNS records.'); + } + }); + } + + /** + * Delete a subdomain and its DNS records + * + * @param ServerSubdomain $serverSubdomain + * @return void + * @throws \Exception + */ + public function deleteSubdomain(ServerSubdomain $serverSubdomain): void + { + $domain = $serverSubdomain->domain; + + DB::transaction(function () use ($serverSubdomain, $domain) { + $dnsRecordsToDelete = $serverSubdomain->dns_records ?? []; + + try { + // First, mark as inactive in database to prevent new operations + $serverSubdomain->update(['is_active' => false]); + + // Try to delete DNS records if provider is available + if (!empty($dnsRecordsToDelete)) { + try { + $dnsProvider = $this->getDnsProvider($domain); + + // Delete DNS records - don't fail the entire operation if some DNS deletions fail + foreach ($dnsRecordsToDelete as $recordId) { + try { + $dnsProvider->deleteRecord($domain->name, $recordId); + } catch (\Exception $e) { + Log::warning("Failed to delete DNS record {$recordId} during subdomain deletion: {$e->getMessage()}"); + } + } + + } catch (\Exception $e) { + // DNS provider unavailable, log and continue with database deletion + Log::warning("DNS provider unavailable during subdomain deletion for {$serverSubdomain->full_domain}: {$e->getMessage()}"); + } + } + + // Delete database record - this should always succeed after marking inactive + $serverSubdomain->delete(); + + } catch (\Exception $e) { + Log::error("Failed to delete subdomain {$serverSubdomain->full_domain}: {$e->getMessage()}"); + throw new \Exception('Failed to delete subdomain completely.'); + } + }); + } + + /** + * Get available domains for subdomain creation. + * Only returns safe data needed by the frontend. + * + * @return array + */ + public function getAvailableDomains(): array + { + return Domain::where('is_active', true) + ->select(['id', 'name', 'is_active', 'is_default']) + ->get() + ->map(function ($domain) { + return [ + 'id' => $domain->id, + 'name' => $domain->name, + 'is_active' => $domain->is_active, + 'is_default' => $domain->is_default, + ]; + }) + ->toArray(); + } + + /** + * Check if a subdomain is available for use. + * + * @param string $subdomain + * @param Domain $domain + * @param Server|null $excludeServer Exclude this server's subdomain from availability check + * @return array + */ + public function checkSubdomainAvailability(string $subdomain, Domain $domain, ?Server $excludeServer = null): array + { + $subdomain = strtolower(trim($subdomain)); + + if (in_array($subdomain, $this->getReservedSubdomains())) { + return [ + 'available' => false, + 'message' => 'This subdomain is reserved and cannot be used.', + ]; + } + + if (empty($subdomain)) { + return [ + 'available' => false, + 'message' => 'Subdomain cannot be empty.', + ]; + } + + if (strlen($subdomain) > 63) { + return [ + 'available' => false, + 'message' => 'Subdomain cannot be longer than 63 characters.', + ]; + } + + if (!preg_match('/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/', $subdomain)) { + return [ + 'available' => false, + 'message' => 'Subdomain contains invalid characters.', + ]; + } + + if (preg_match('/[<>"\']/', $subdomain)) { + return [ + 'available' => false, + 'message' => 'Subdomain contains invalid characters.', + ]; + } + + $query = ServerSubdomain::where('domain_id', $domain->id) + ->where('subdomain', $subdomain) + ->where('is_active', true); + + if ($excludeServer) { + $query->where('server_id', '!=', $excludeServer->id); + } + + $existing = $query->exists(); + + return [ + 'available' => !$existing, + 'message' => $existing ? 'Subdomain is not available.' : 'Subdomain is available.', + ]; + } + + /** + * Get the list of reserved subdomains. + * + * @return array + */ + public function getReservedSubdomains(): array + { + //? I used ChatGPT to generate this list based on common reserved subdomains + //? AND I AM PROUD - ellie + + return array_map('strtolower', [ + // Web & infra + 'www', 'web', 'origin', 'edge', 'cdn', 'static', 'assets', 'files', 'media', 'img', 'images', 'downloads', 'dl', + + // Email & DNS + 'mail', 'webmail', 'smtp', 'imap', 'pop', 'pop3', 'mx', 'dns', 'ns', + 'autodiscover', 'autoconfig', 'mta-sts', 'openpgpkey', + + // Admin & control planes + 'admin', 'administrator', 'root', 'sysadmin', 'panel', 'cp', 'cpanel', 'whm', 'webdisk', + 'status', 'monitor', 'monitoring', 'health', 'uptime', 'metrics', 'logs', 'logging', + + // Auth & accounts + 'login', 'signin', 'sign-in', 'signup', 'sign-up', 'register', 'account', 'accounts', + 'auth', 'oauth', 'oauth2', 'sso', 'id', 'identity', 'secure', 'passwd', 'password', + + // APIs & developer endpoints + 'api', 'apis', 'dev', 'development', 'test', 'testing', 'qa', 'stage', 'staging', + 'preprod', 'preview', 'sandbox', 'prod', 'production', 'demo', + + // Commerce / portals / docs + 'billing', 'invoice', 'invoices', 'payments', 'pay', 'store', 'shop', + 'support', 'help', 'docs', 'documentation', 'wiki', 'portal', 'dashboard', + + // File transfer + 'ftp', 'sftp', 'tftp', + + // CI/CD & tooling often probed by scanners + 'git', 'github', 'gitlab', 'gitea', 'bitbucket', 'hg', 'svn', + 'jenkins', 'grafana', 'prometheus', 'kibana', 'elastic', 'sonarqube', 'nexus', + + // Misc safety + 'localhost', 'local', 'loopback', 'default', 'undefined', 'null', + ]); + } + + /** + * Get the subdomain feature for a server. + * + * @return null|\Pterodactyl\Contracts\Subdomain\SubdomainFeatureInterface + */ + public function getServerSubdomainFeature(Server $server): ?SubdomainFeatureInterface + { + if (!$server->egg) { + return null; + } + + // Check in egg's direct features + if (is_array($server->egg->features)) { + foreach ($server->egg->features as $featureName) { + if (isset($this->subdomainFeatures[$featureName])) { + $featureClass = $this->subdomainFeatures[$featureName]; + return new $featureClass(); + } + } + } + + // Check in inherited features + if (is_array($server->egg->inherit_features)) { + foreach ($server->egg->inherit_features as $featureName) { + if (isset($this->subdomainFeatures[$featureName])) { + $featureClass = $this->subdomainFeatures[$featureName]; + return new $featureClass(); + } + } + } + + return null; + } + + /** + * Validate a subdomain name. + * + * @param string $subdomain + * @param SubdomainFeatureInterface $feature + * @param Domain $domain + * @return void + * @throws \Exception + */ + private function validateSubdomain(string $subdomain, SubdomainFeatureInterface $feature, Domain $domain): void + { + $availabilityResult = $this->checkSubdomainAvailability($subdomain, $domain, null); + + if (!$availabilityResult['available']) { + throw new \Exception($availabilityResult['message']); + } + } + + /** + * Get the DNS provider for a domain. + * + * @param Domain $domain + * @return \Pterodactyl\Contracts\Dns\DnsProviderInterface + * @throws \Exception + */ + private function getDnsProvider(Domain $domain): DnsProviderInterface + { + $providerName = $domain->dns_provider; + + if (!isset($this->dnsProviders[$providerName])) { + throw new \Exception("Unsupported DNS provider: {$providerName}"); + } + + $providerClass = $this->dnsProviders[$providerName]; + return new $providerClass($domain->dns_config); + } + + /** + * Clean up DNS records in case of failure. + * + * @param DnsProviderInterface $dnsProvider + * @param string $domain + * @param array $recordIds + * @return void + */ + private function cleanupDnsRecords(DnsProviderInterface $dnsProvider, string $domain, array $recordIds): void + { + foreach ($recordIds as $recordId) { + try { + $dnsProvider->deleteRecord($domain, $recordId); + } catch (\Exception $cleanupException) { + Log::error("Failed to cleanup DNS record {$recordId} for domain {$domain} during rollback"); + } + } + } + + /** + * Attempt to rollback DNS changes during update failures. + * + * @param DnsProviderInterface $dnsProvider + * @param string $domain + * @param array $rollbackData + * @param array $createdRecordIds + * @return void + */ + private function rollbackDnsChanges(DnsProviderInterface $dnsProvider, string $domain, array $rollbackData, array $createdRecordIds): void + { + // Rollback updated records to original state + foreach ($rollbackData as $recordId => $originalRecord) { + try { + $dnsProvider->updateRecord( + $domain, + $recordId, + $originalRecord['content'] ?? $originalRecord['data'] ?? '', + $originalRecord['ttl'] ?? null + ); + } catch (\Exception $e) { + Log::error("Failed to rollback DNS record {$recordId} during update failure"); + } + } + + // Delete any newly created records + $this->cleanupDnsRecords($dnsProvider, $domain, array_diff($createdRecordIds, array_keys($rollbackData))); + } + + /** + * Normalize IP addresses in DNS records (convert localhost to 127.0.0.1). + * + * @param array $dnsRecords + * @return array + */ + private function normalizeIpAddresses(array $dnsRecords): array + { + foreach ($dnsRecords as &$record) { + if ($record['type'] === 'A' && isset($record['content'])) { + // Convert localhost to 127.0.0.1 for A records + if (strtolower($record['content']) === 'localhost') { + $record['content'] = '127.0.0.1'; + } + } + } + + return $dnsRecords; + } +} \ No newline at end of file diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index 6347dfec3..546b4e26a 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -40,7 +40,7 @@ class NodeTransformer extends BaseTransformer $response[$node->getUpdatedAtColumn()] = $this->formatTimestamp($node->updated_at); $response[$node->getCreatedAtColumn()] = $this->formatTimestamp($node->created_at); - $resources = $node->servers()->select(['memory', 'disk'])->get(); + $resources = $node->servers()->where('exclude_from_resource_calculation', false)->select(['memory', 'disk'])->get(); $response['allocated_resources'] = [ 'memory' => $resources->sum('memory'), diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index e5db01fb2..1339b3b60 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -62,12 +62,14 @@ class ServerTransformer extends BaseTransformer 'suspended' => $server->isSuspended(), 'limits' => [ 'memory' => $server->memory, + 'overhead_memory' => $server->overhead_memory, 'swap' => $server->swap, 'disk' => $server->disk, 'io' => $server->io, 'cpu' => $server->cpu, 'threads' => $server->threads, 'oom_disabled' => $server->oom_disabled, + 'exclude_from_resource_calculation' => $server->exclude_from_resource_calculation, ], 'feature_limits' => [ 'databases' => $server->database_limit, diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php index 298c99642..fa102b9f3 100644 --- a/app/Transformers/Api/Client/BackupTransformer.php +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -3,9 +3,15 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Backup; +use Pterodactyl\Services\Backups\ServerStateService; class BackupTransformer extends BaseClientTransformer { + public function __construct( + private ServerStateService $serverStateService, + ) { + } + public function getResourceName(): string { return Backup::RESOURCE_NAME; @@ -13,7 +19,7 @@ class BackupTransformer extends BaseClientTransformer public function transform(Backup $backup): array { - return [ + $data = [ 'uuid' => $backup->uuid, 'is_successful' => $backup->is_successful, 'is_locked' => $backup->is_locked, @@ -24,5 +30,20 @@ class BackupTransformer extends BaseClientTransformer 'created_at' => $backup->created_at->toAtomString(), 'completed_at' => $backup->completed_at ? $backup->completed_at->toAtomString() : null, ]; + + // Add server state information if available + $stateSummary = $this->serverStateService->getStateSummary($backup); + $data['server_state'] = $stateSummary ? [ + 'has_state' => true, + 'nest_name' => $stateSummary['nest_name'], + 'egg_name' => $stateSummary['egg_name'], + 'image' => $stateSummary['image'], + 'variables_count' => $stateSummary['variables_count'], + 'captured_at' => $stateSummary['captured_at'], + ] : [ + 'has_state' => false, + ]; + + return $data; } } diff --git a/app/Transformers/Api/Client/EggController.php b/app/Transformers/Api/Client/EggController.php deleted file mode 100644 index a00c793b3..000000000 --- a/app/Transformers/Api/Client/EggController.php +++ /dev/null @@ -1,33 +0,0 @@ -fractal->collection($nest->eggs) - ->transformWith($this->getTransformer(EggTransformer::class)) - ->toArray(); - } - - /** - * Return a single egg that exists on the specified nest. - */ - public function view(GetEggRequest $request, Nest $nest, Egg $egg): array - { - return $this->fractal->item($egg) - ->transformWith($this->getTransformer(EggTransformer::class)) - ->toArray(); - } -} diff --git a/app/Transformers/Api/Client/ServerSubdomainTransformer.php b/app/Transformers/Api/Client/ServerSubdomainTransformer.php new file mode 100644 index 000000000..d279f64fc --- /dev/null +++ b/app/Transformers/Api/Client/ServerSubdomainTransformer.php @@ -0,0 +1,39 @@ +resourceName; + } + + /** + * Transform a ServerSubdomain model into a representation for the client API. + */ + public function transform(ServerSubdomain $model): array + { + return [ + 'subdomain' => $model->subdomain, + 'domain' => $model->domain->name, + 'domain_id' => $model->domain_id, + 'full_domain' => $model->full_domain, + 'record_type' => $model->record_type, + 'is_active' => $model->is_active, + 'created_at' => $model->created_at->toISOString(), + 'updated_at' => $model->updated_at->toISOString(), + ]; + } +} \ No newline at end of file diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index e5b5f2654..28d3dc1fc 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -55,6 +55,7 @@ class ServerTransformer extends BaseClientTransformer 'description' => $server->description, 'limits' => [ 'memory' => $server->memory, + 'overhead_memory' => $server->overhead_memory, 'swap' => $server->swap, 'disk' => $server->disk, 'io' => $server->io, diff --git a/app/helpers.php b/app/helpers.php index 59aa7166a..c0e531f32 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -33,3 +33,19 @@ if (!function_exists('object_get_strict')) { return $object; } } + +if (!function_exists('humanizeSize')) { + /** + * Convert bytes to a human readable format with binary units. + */ + function humanizeSize(int|float $bytes): string + { + $units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']; + $i = 0; + while ($bytes >= 1024 && $i < count($units) - 1) { + $bytes /= 1024; + $i++; + } + return round($bytes, 2) . ' ' . $units[$i]; + } +} diff --git a/composer.lock b/composer.lock index 9cc28c9eb..32686e826 100644 --- a/composer.lock +++ b/composer.lock @@ -160,16 +160,16 @@ }, { "name": "brick/math", - "version": "0.12.1", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "shasum": "" }, "require": { @@ -178,7 +178,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -208,7 +208,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/brick/math/tree/0.12.3" }, "funding": [ { @@ -216,7 +216,7 @@ "type": "github" } ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-02-28T13:11:00+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -597,16 +597,16 @@ }, { "name": "egulias/email-validator", - "version": "4.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "b115554301161fa21467629f1e1391c1936de517" + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517", - "reference": "b115554301161fa21467629f1e1391c1936de517", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", "shasum": "" }, "require": { @@ -652,7 +652,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.3" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, "funding": [ { @@ -660,7 +660,7 @@ "type": "github" } ], - "time": "2024-12-27T00:36:43+00:00" + "time": "2025-03-06T22:45:56+00:00" }, { "name": "fruitcake/php-cors", @@ -797,16 +797,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.2", + "version": "7.9.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", "shasum": "" }, "require": { @@ -903,7 +903,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" }, "funding": [ { @@ -919,20 +919,20 @@ "type": "tidelift" } ], - "time": "2024-07-24T11:22:20+00:00" + "time": "2025-03-27T13:37:11+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.4", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", "shasum": "" }, "require": { @@ -986,7 +986,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.4" + "source": "https://github.com/guzzle/promises/tree/2.2.0" }, "funding": [ { @@ -1002,20 +1002,20 @@ "type": "tidelift" } ], - "time": "2024-10-17T10:06:22+00:00" + "time": "2025-03-27T13:27:01+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", "shasum": "" }, "require": { @@ -1102,7 +1102,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.0" + "source": "https://github.com/guzzle/psr7/tree/2.7.1" }, "funding": [ { @@ -1118,7 +1118,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T11:15:46+00:00" + "time": "2025-03-27T12:30:47+00:00" }, { "name": "guzzlehttp/uri-template", @@ -1277,20 +1277,20 @@ }, { "name": "laracasts/utilities", - "version": "3.2.3", + "version": "3.2.4", "source": { "type": "git", "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", - "reference": "4826fe062f58d07b13e72460141b21c8c07b736d" + "reference": "6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/4826fe062f58d07b13e72460141b21c8c07b736d", - "reference": "4826fe062f58d07b13e72460141b21c8c07b736d", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146", + "reference": "6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146", "shasum": "" }, "require": { - "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "php": ">=5.5.0|>=7.2.5|>=8.0.0" }, "require-dev": { @@ -1332,9 +1332,9 @@ ], "support": { "issues": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/issues", - "source": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/tree/3.2.3" + "source": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/tree/3.2.4" }, - "time": "2024-03-03T16:53:37+00:00" + "time": "2025-02-27T15:16:31+00:00" }, { "name": "laravel/framework", @@ -1990,16 +1990,16 @@ }, { "name": "league/commonmark", - "version": "2.6.1", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "d990688c91cedfb69753ffc2512727ec646df2ad" + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad", - "reference": "d990688c91cedfb69753ffc2512727ec646df2ad", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", "shasum": "" }, "require": { @@ -2036,7 +2036,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.7-dev" + "dev-main": "2.8-dev" } }, "autoload": { @@ -2093,7 +2093,7 @@ "type": "tidelift" } ], - "time": "2024-12-29T14:10:59+00:00" + "time": "2025-05-05T12:20:28+00:00" }, { "name": "league/config", @@ -2414,16 +2414,16 @@ }, { "name": "league/fractal", - "version": "0.20.1", + "version": "0.20.2", "source": { "type": "git", "url": "https://github.com/thephpleague/fractal.git", - "reference": "8b9d39b67624db9195c06f9c1ffd0355151eaf62" + "reference": "573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/8b9d39b67624db9195c06f9c1ffd0355151eaf62", - "reference": "8b9d39b67624db9195c06f9c1ffd0355151eaf62", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e", + "reference": "573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e", "shasum": "" }, "require": { @@ -2432,18 +2432,18 @@ "require-dev": { "doctrine/orm": "^2.5", "illuminate/contracts": "~5.0", + "laminas/laminas-paginator": "~2.12", "mockery/mockery": "^1.3", - "pagerfanta/pagerfanta": "~1.0.0", + "pagerfanta/pagerfanta": "~1.0.0|~4.0.0", "phpstan/phpstan": "^1.4", "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "~3.4", - "vimeo/psalm": "^4.22", - "zendframework/zend-paginator": "~2.3" + "vimeo/psalm": "^4.30" }, "suggest": { "illuminate/pagination": "The Illuminate Pagination component.", - "pagerfanta/pagerfanta": "Pagerfanta Paginator", - "zendframework/zend-paginator": "Zend Framework Paginator" + "laminas/laminas-paginator": "Laminas Framework Paginator", + "pagerfanta/pagerfanta": "Pagerfanta Paginator" }, "type": "library", "extra": { @@ -2478,9 +2478,9 @@ ], "support": { "issues": "https://github.com/thephpleague/fractal/issues", - "source": "https://github.com/thephpleague/fractal/tree/0.20.1" + "source": "https://github.com/thephpleague/fractal/tree/0.20.2" }, - "time": "2022-04-11T12:47:17+00:00" + "time": "2025-02-14T21:33:14+00:00" }, { "name": "league/mime-type-detection", @@ -2591,16 +2591,16 @@ }, { "name": "monolog/monolog", - "version": "3.8.1", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4" + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4", - "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", "shasum": "" }, "require": { @@ -2678,7 +2678,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.8.1" + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" }, "funding": [ { @@ -2690,7 +2690,7 @@ "type": "tidelift" } ], - "time": "2024-12-05T17:15:07+00:00" + "time": "2025-03-24T10:02:05+00:00" }, { "name": "mtdowling/jmespath.php", @@ -2760,16 +2760,16 @@ }, { "name": "nesbot/carbon", - "version": "3.8.5", + "version": "3.9.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "b1a53a27898639579a67de42e8ced5d5386aa9a4" + "reference": "ced71f79398ece168e24f7f7710462f462310d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/b1a53a27898639579a67de42e8ced5d5386aa9a4", - "reference": "b1a53a27898639579a67de42e8ced5d5386aa9a4", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d", + "reference": "ced71f79398ece168e24f7f7710462f462310d4d", "shasum": "" }, "require": { @@ -2862,7 +2862,7 @@ "type": "tidelift" } ], - "time": "2025-02-11T16:28:45+00:00" + "time": "2025-05-01T19:51:51+00:00" }, { "name": "nette/schema", @@ -2928,16 +2928,16 @@ }, { "name": "nette/utils", - "version": "v4.0.5", + "version": "v4.0.6", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" + "reference": "ce708655043c7050eb050df361c5e313cf708309" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", - "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309", + "reference": "ce708655043c7050eb050df361c5e313cf708309", "shasum": "" }, "require": { @@ -3008,9 +3008,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.5" + "source": "https://github.com/nette/utils/tree/v4.0.6" }, - "time": "2024-08-07T15:39:19+00:00" + "time": "2025-03-30T21:06:30+00:00" }, { "name": "nikic/php-parser", @@ -3072,31 +3072,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.8" + "symfony/console": "^7.2.6" }, "require-dev": { - "illuminate/console": "^11.33.2", - "laravel/pint": "^1.18.2", + "illuminate/console": "^11.44.7", + "laravel/pint": "^1.22.0", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.11", - "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.8", + "pestphp/pest": "^2.36.0 || ^3.8.2", + "phpstan/phpstan": "^1.12.25", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.2.6", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -3139,7 +3139,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" }, "funding": [ { @@ -3155,7 +3155,7 @@ "type": "github" } ], - "time": "2024-11-21T10:39:51+00:00" + "time": "2025-05-08T08:14:37+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -4104,16 +4104,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.7", + "version": "v0.12.8", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" + "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", - "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/85057ceedee50c49d4f6ecaff73ee96adb3b3625", + "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625", "shasum": "" }, "require": { @@ -4177,9 +4177,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.8" }, - "time": "2024-12-10T01:58:33+00:00" + "time": "2025-03-16T03:05:19+00:00" }, { "name": "ralouphie/getallheaders", @@ -4227,16 +4227,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -4244,25 +4244,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -4300,19 +4297,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", @@ -4606,16 +4593,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.19.0", + "version": "1.92.4", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa" + "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa", - "reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d20b1969f836d210459b78683d85c9cd5c5f508c", + "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c", "shasum": "" }, "require": { @@ -4626,6 +4613,7 @@ "mockery/mockery": "^1.5", "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", "pestphp/pest": "^1.23|^2.1|^3.1", + "phpunit/php-code-coverage": "^9.0|^10.0|^11.0", "phpunit/phpunit": "^9.5.24|^10.5|^11.5", "spatie/pest-plugin-test-time": "^1.1|^2.2" }, @@ -4654,7 +4642,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.19.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.4" }, "funding": [ { @@ -4662,7 +4650,7 @@ "type": "github" } ], - "time": "2025-02-06T14:58:20+00:00" + "time": "2025-04-11T15:27:14+00:00" }, { "name": "spatie/laravel-query-builder", @@ -4740,16 +4728,16 @@ }, { "name": "staudenmeir/belongs-to-through", - "version": "v2.16.3", + "version": "v2.16.4", "source": { "type": "git", "url": "https://github.com/staudenmeir/belongs-to-through.git", - "reference": "33f8b614bf2e84bf818911981326654a4a0c8741" + "reference": "451496e4272c11d87b404791acdd352c606f29c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/33f8b614bf2e84bf818911981326654a4a0c8741", - "reference": "33f8b614bf2e84bf818911981326654a4a0c8741", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/451496e4272c11d87b404791acdd352c606f29c0", + "reference": "451496e4272c11d87b404791acdd352c606f29c0", "shasum": "" }, "require": { @@ -4795,7 +4783,7 @@ "description": "Laravel Eloquent BelongsToThrough relationships", "support": { "issues": "https://github.com/staudenmeir/belongs-to-through/issues", - "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.16.3" + "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.16.4" }, "funding": [ { @@ -4803,7 +4791,7 @@ "type": "custom" } ], - "time": "2025-02-02T11:46:25+00:00" + "time": "2025-02-20T19:20:43+00:00" }, { "name": "symfony/clock", @@ -4881,16 +4869,16 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218", "shasum": "" }, "require": { @@ -4954,7 +4942,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.2.6" }, "funding": [ { @@ -4970,7 +4958,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/css-selector", @@ -5106,16 +5094,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.2.3", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49" + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/959a74d044a6db21f4caa6d695648dcb5584cb49", - "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", "shasum": "" }, "require": { @@ -5161,7 +5149,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.3" + "source": "https://github.com/symfony/error-handler/tree/v7.2.5" }, "funding": [ { @@ -5177,7 +5165,7 @@ "type": "tidelift" } ], - "time": "2025-01-07T09:39:55+00:00" + "time": "2025-03-03T07:12:39+00:00" }, { "name": "symfony/event-dispatcher", @@ -5573,16 +5561,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0" + "reference": "6023ec7607254c87c5e69fb3558255aca440d72b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6023ec7607254c87c5e69fb3558255aca440d72b", + "reference": "6023ec7607254c87c5e69fb3558255aca440d72b", "shasum": "" }, "require": { @@ -5631,7 +5619,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.6" }, "funding": [ { @@ -5647,20 +5635,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T10:56:55+00:00" + "time": "2025-04-09T08:14:01+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b" + "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", - "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9dec01e6094a063e738f8945ef69c0cfcf792ec", + "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec", "shasum": "" }, "require": { @@ -5745,7 +5733,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.3" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.6" }, "funding": [ { @@ -5761,20 +5749,20 @@ "type": "tidelift" } ], - "time": "2025-01-29T07:40:13+00:00" + "time": "2025-05-02T09:04:03+00:00" }, { "name": "symfony/mailer", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3" + "reference": "998692469d6e698c6eadc7ef37a6530a9eabb356" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/f3871b182c44997cf039f3b462af4a48fb85f9d3", - "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3", + "url": "https://api.github.com/repos/symfony/mailer/zipball/998692469d6e698c6eadc7ef37a6530a9eabb356", + "reference": "998692469d6e698c6eadc7ef37a6530a9eabb356", "shasum": "" }, "require": { @@ -5825,7 +5813,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.2.3" + "source": "https://github.com/symfony/mailer/tree/v7.2.6" }, "funding": [ { @@ -5841,7 +5829,7 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-04-04T09:50:51+00:00" }, { "name": "symfony/mailgun-mailer", @@ -5914,16 +5902,16 @@ }, { "name": "symfony/mime", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204" + "reference": "706e65c72d402539a072d0d6ad105fff6c161ef1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2fc3b4bd67e4747e45195bc4c98bea4628476204", - "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204", + "url": "https://api.github.com/repos/symfony/mime/zipball/706e65c72d402539a072d0d6ad105fff6c161ef1", + "reference": "706e65c72d402539a072d0d6ad105fff6c161ef1", "shasum": "" }, "require": { @@ -5978,7 +5966,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.3" + "source": "https://github.com/symfony/mime/tree/v7.2.6" }, "funding": [ { @@ -5994,11 +5982,11 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-04-27T13:34:41+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -6057,7 +6045,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -6077,7 +6065,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -6135,7 +6123,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -6155,16 +6143,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -6218,7 +6206,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" }, "funding": [ { @@ -6234,11 +6222,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -6299,7 +6287,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -6319,19 +6307,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -6379,7 +6368,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -6395,20 +6384,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -6459,7 +6448,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -6475,11 +6464,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", @@ -6535,7 +6524,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" }, "funding": [ { @@ -6555,7 +6544,7 @@ }, { "name": "symfony/polyfill-uuid", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -6614,7 +6603,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" }, "funding": [ { @@ -6704,16 +6693,16 @@ }, { "name": "symfony/process", - "version": "v7.2.0", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", "shasum": "" }, "require": { @@ -6745,7 +6734,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.0" + "source": "https://github.com/symfony/process/tree/v7.2.5" }, "funding": [ { @@ -6761,7 +6750,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2025-03-13T12:21:46+00:00" }, { "name": "symfony/routing", @@ -6929,16 +6918,16 @@ }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931", "shasum": "" }, "require": { @@ -6996,7 +6985,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v7.2.6" }, "funding": [ { @@ -7012,20 +7001,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-04-20T20:18:16+00:00" }, { "name": "symfony/translation", - "version": "v7.2.2", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923" + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923", - "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923", + "url": "https://api.github.com/repos/symfony/translation/zipball/e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", "shasum": "" }, "require": { @@ -7091,7 +7080,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.2.2" + "source": "https://github.com/symfony/translation/tree/v7.2.6" }, "funding": [ { @@ -7107,7 +7096,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:18:10+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/translation-contracts", @@ -7263,16 +7252,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" + "reference": "9c46038cd4ed68952166cf7001b54eb539184ccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9c46038cd4ed68952166cf7001b54eb539184ccb", + "reference": "9c46038cd4ed68952166cf7001b54eb539184ccb", "shasum": "" }, "require": { @@ -7326,7 +7315,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.6" }, "funding": [ { @@ -7342,7 +7331,7 @@ "type": "tidelift" } ], - "time": "2025-01-17T11:39:41+00:00" + "time": "2025-04-09T08:14:01+00:00" }, { "name": "symfony/yaml", @@ -7472,16 +7461,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", "shasum": "" }, "require": { @@ -7540,7 +7529,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" }, "funding": [ { @@ -7552,7 +7541,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:52:34+00:00" + "time": "2025-04-30T23:37:27+00:00" }, { "name": "voku/portable-ascii", @@ -7900,16 +7889,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "ffe442c5974c44a9343e37a0abcb1cc37319f5b9" + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/ffe442c5974c44a9343e37a0abcb1cc37319f5b9", - "reference": "ffe442c5974c44a9343e37a0abcb1cc37319f5b9", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", "shasum": "" }, "require": { @@ -7953,7 +7942,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.6.0" + "source": "https://github.com/composer/class-map-generator/tree/1.6.1" }, "funding": [ { @@ -7969,7 +7958,7 @@ "type": "tidelift" } ], - "time": "2025-02-05T10:05:34+00:00" + "time": "2025-03-24T13:50:44+00:00" }, { "name": "composer/pcre", @@ -8199,26 +8188,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -8238,9 +8230,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "evenement/evenement", @@ -8415,16 +8407,16 @@ }, { "name": "filp/whoops", - "version": "2.17.0", + "version": "2.18.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e" + "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e", + "url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", + "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", "shasum": "" }, "require": { @@ -8474,7 +8466,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.17.0" + "source": "https://github.com/filp/whoops/tree/2.18.0" }, "funding": [ { @@ -8482,7 +8474,7 @@ "type": "github" } ], - "time": "2025-01-25T12:00:00+00:00" + "time": "2025-03-15T12:00:00+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -8589,20 +8581,20 @@ }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -8610,8 +8602,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -8634,9 +8626,9 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { "name": "itsgoingd/clockwork", @@ -9031,16 +9023,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -9079,7 +9071,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -9087,7 +9079,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nunomaduro/collision", @@ -9417,16 +9409,16 @@ }, { "name": "phpmyadmin/sql-parser", - "version": "5.10.3", + "version": "5.11.0", "source": { "type": "git", "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "5346664973d10cf1abff20837fb1183f3c11a055" + "reference": "07044bc8c13abd542756c3fd34dc66a5d6dee8e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/5346664973d10cf1abff20837fb1183f3c11a055", - "reference": "5346664973d10cf1abff20837fb1183f3c11a055", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/07044bc8c13abd542756c3fd34dc66a5d6dee8e4", + "reference": "07044bc8c13abd542756c3fd34dc66a5d6dee8e4", "shasum": "" }, "require": { @@ -9441,9 +9433,11 @@ "phpbench/phpbench": "^1.1", "phpmyadmin/coding-standard": "^3.0", "phpmyadmin/motranslator": "^4.0 || ^5.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.9.12", - "phpstan/phpstan-phpunit": "^1.3.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^1.12", + "phpstan/phpstan-deprecation-rules": "^1.2", + "phpstan/phpstan-phpunit": "^1.4", + "phpstan/phpstan-strict-rules": "^1.6", "phpunit/phpunit": "^8.5 || ^9.6", "psalm/plugin-phpunit": "^0.16.1", "vimeo/psalm": "^4.11", @@ -9500,20 +9494,20 @@ "type": "other" } ], - "time": "2025-01-19T04:14:02+00:00" + "time": "2025-02-22T20:00:59+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", "shasum": "" }, "require": { @@ -9545,22 +9539,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" }, - "time": "2024-10-13T11:29:49+00:00" + "time": "2025-02-19T13:28:12+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.17", + "version": "1.12.25", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "7027b3b0270bf392de0cfba12825979768d728bf" + "reference": "e310849a19e02b8bfcbb63147f495d8f872dd96f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7027b3b0270bf392de0cfba12825979768d728bf", - "reference": "7027b3b0270bf392de0cfba12825979768d728bf", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e310849a19e02b8bfcbb63147f495d8f872dd96f", + "reference": "e310849a19e02b8bfcbb63147f495d8f872dd96f", "shasum": "" }, "require": { @@ -9605,7 +9599,7 @@ "type": "github" } ], - "time": "2025-02-07T15:01:57+00:00" + "time": "2025-04-27T12:20:45+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9930,16 +9924,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.45", + "version": "10.5.46", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" + "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d", + "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d", "shasum": "" }, "require": { @@ -9949,7 +9943,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -10011,7 +10005,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.46" }, "funding": [ { @@ -10022,12 +10016,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-02-06T16:08:12+00:00" + "time": "2025-05-02T06:46:24+00:00" }, { "name": "react/cache", @@ -11473,16 +11475,16 @@ }, { "name": "spatie/backtrace", - "version": "1.7.1", + "version": "1.7.4", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "0f2477c520e3729de58e061b8192f161c99f770b" + "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/0f2477c520e3729de58e061b8192f161c99f770b", - "reference": "0f2477c520e3729de58e061b8192f161c99f770b", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/cd37a49fce7137359ac30ecc44ef3e16404cccbe", + "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe", "shasum": "" }, "require": { @@ -11520,7 +11522,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.7.1" + "source": "https://github.com/spatie/backtrace/tree/1.7.4" }, "funding": [ { @@ -11532,34 +11534,34 @@ "type": "other" } ], - "time": "2024-12-02T13:28:15+00:00" + "time": "2025-05-08T15:41:09+00:00" }, { "name": "spatie/error-solutions", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541" + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d239a65235a1eb128dfa0a4e4c4ef032ea11b541", - "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", "shasum": "" }, "require": { "php": "^8.0" }, "require-dev": { - "illuminate/broadcasting": "^10.0|^11.0", - "illuminate/cache": "^10.0|^11.0", - "illuminate/support": "^10.0|^11.0", - "livewire/livewire": "^2.11|^3.3.5", + "illuminate/broadcasting": "^10.0|^11.0|^12.0", + "illuminate/cache": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "livewire/livewire": "^2.11|^3.5.20", "openai-php/client": "^0.10.1", - "orchestra/testbench": "^7.0|8.22.3|^9.0", - "pestphp/pest": "^2.20", - "phpstan/phpstan": "^1.11", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.20|^3.0", + "phpstan/phpstan": "^2.1", "psr/simple-cache": "^3.0", "psr/simple-cache-implementation": "^3.0", "spatie/ray": "^1.28", @@ -11598,7 +11600,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/1.1.2" + "source": "https://github.com/spatie/error-solutions/tree/1.1.3" }, "funding": [ { @@ -11606,24 +11608,24 @@ "type": "github" } ], - "time": "2024-12-11T09:51:56+00:00" + "time": "2025-02-14T12:29:50+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.10.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272" + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", - "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/bf1716eb98bd689451b071548ae9e70738dce62f", + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0", "php": "^8.0", "spatie/backtrace": "^1.6.1", "symfony/http-foundation": "^5.2|^6.0|^7.0", @@ -11667,7 +11669,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.10.0" + "source": "https://github.com/spatie/flare-client-php/tree/1.10.1" }, "funding": [ { @@ -11675,20 +11677,20 @@ "type": "github" } ], - "time": "2024-12-02T14:30:06+00:00" + "time": "2025-02-14T13:42:06+00:00" }, { "name": "spatie/ignition", - "version": "1.15.0", + "version": "1.15.1", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2" + "reference": "31f314153020aee5af3537e507fef892ffbf8c85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/e3a68e137371e1eb9edc7f78ffa733f3b98991d2", - "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2", + "url": "https://api.github.com/repos/spatie/ignition/zipball/31f314153020aee5af3537e507fef892ffbf8c85", + "reference": "31f314153020aee5af3537e507fef892ffbf8c85", "shasum": "" }, "require": { @@ -11701,7 +11703,7 @@ "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { - "illuminate/cache": "^9.52|^10.0|^11.0", + "illuminate/cache": "^9.52|^10.0|^11.0|^12.0", "mockery/mockery": "^1.4", "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", @@ -11758,7 +11760,7 @@ "type": "github" } ], - "time": "2024-06-12T14:55:22+00:00" + "time": "2025-02-21T14:31:39+00:00" }, { "name": "spatie/laravel-ignition", @@ -11986,7 +11988,7 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -12042,7 +12044,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" }, "funding": [ { @@ -12062,16 +12064,16 @@ }, { "name": "symfony/stopwatch", - "version": "v7.2.2", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df" + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e46690d5b9d7164a6d061cab1e8d46141b9f49df", - "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { @@ -12104,7 +12106,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.2.2" + "source": "https://github.com/symfony/stopwatch/tree/v7.2.4" }, "funding": [ { @@ -12120,7 +12122,7 @@ "type": "tidelift" } ], - "time": "2024-12-18T14:28:33+00:00" + "time": "2025-02-24T10:49:57+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/app.php b/config/app.php index 4eaea69a1..6497ef694 100644 --- a/config/app.php +++ b/config/app.php @@ -3,231 +3,235 @@ use Illuminate\Support\Facades\Facade; return [ - /* - |-------------------------------------------------------------------------- - | Application Version - |-------------------------------------------------------------------------- - | This value is set when creating a Pterodactyl release. You should not - | change this value if you are not maintaining your own internal versions. - */ + /* + |-------------------------------------------------------------------------- + | Application Version + |-------------------------------------------------------------------------- + | This value is set when creating a Pterodactyl release. You should not + | change this value if you are not maintaining your own internal versions. + */ - 'version' => '3.0.0', + 'version' => 'canary', + + /* + |-------------------------------------------------------------------------- + | Application Name + |-------------------------------------------------------------------------- + | + | This value is the name of your application, which will be used when the + | framework needs to place the application's name in a notification or + | other UI elements where an application name needs to be displayed. + | + */ + + 'name' => env('APP_NAME', 'Pyrodactyl'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => env('APP_TIMEZONE', 'UTC'), + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + + /* + |-------------------------------------------------------------------------- + | Exception Reporter Configuration + |-------------------------------------------------------------------------- + | + | If you're encountering weird behavior with the Panel and no exceptions + | are being logged try changing the environment variable below to be true. + | This will override the default "don't report" behavior of the Panel and log + | all exceptions. This will be quite noisy. + | + */ + + 'exceptions' => [ + 'report_all' => env('APP_REPORT_ALL_EXCEPTIONS', false), + ], + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => [ + /* + * Laravel Framework Service Providers... + */ + Illuminate\Auth\AuthServiceProvider::class, + Illuminate\Broadcasting\BroadcastServiceProvider::class, + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, + Illuminate\View\ViewServiceProvider::class, /* - |-------------------------------------------------------------------------- - | Application Name - |-------------------------------------------------------------------------- - | - | This value is the name of your application, which will be used when the - | framework needs to place the application's name in a notification or - | other UI elements where an application name needs to be displayed. - | - */ - - 'name' => env('APP_NAME', 'Pyrodactyl'), + * Application Service Providers... + */ + Pterodactyl\Providers\ActivityLogServiceProvider::class, + Pterodactyl\Providers\AppServiceProvider::class, + Pterodactyl\Providers\AuthServiceProvider::class, + Pterodactyl\Providers\BackupsServiceProvider::class, + Pterodactyl\Providers\BladeServiceProvider::class, + Pterodactyl\Providers\CaptchaServiceProvider::class, + Pterodactyl\Providers\EventServiceProvider::class, + Pterodactyl\Providers\HashidsServiceProvider::class, + Pterodactyl\Providers\ObserverServiceProvider::class, + Pterodactyl\Providers\RouteServiceProvider::class, + Pterodactyl\Providers\RepositoryServiceProvider::class, + Pterodactyl\Providers\ServerOperationServiceProvider::class, + Pterodactyl\Providers\SubdomainServiceProvider::class, + Pterodactyl\Providers\ViewComposerServiceProvider::class, /* - |-------------------------------------------------------------------------- - | Application Environment - |-------------------------------------------------------------------------- - | - | This value determines the "environment" your application is currently - | running in. This may determine how you prefer to configure various - | services the application utilizes. Set this in your ".env" file. - | - */ + * Additional Dependencies + */ + Prologue\Alerts\AlertsServiceProvider::class, + ], - 'env' => env('APP_ENV', 'production'), + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded, so they don't hinder performance. + | + */ - /* - |-------------------------------------------------------------------------- - | Application Debug Mode - |-------------------------------------------------------------------------- - | - | When your application is in debug mode, detailed error messages with - | stack traces will be shown on every error that occurs within your - | application. If disabled, a simple generic error page is shown. - | - */ + 'aliases' => Facade::defaultAliases()->merge([ + 'Alert' => Prologue\Alerts\Facades\Alert::class, + 'Carbon' => Carbon\Carbon::class, + 'JavaScript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, + 'Theme' => Pterodactyl\Extensions\Facades\Theme::class, - 'debug' => (bool) env('APP_DEBUG', false), - - /* - |-------------------------------------------------------------------------- - | Application URL - |-------------------------------------------------------------------------- - | - | This URL is used by the console to properly generate URLs when using - | the Artisan command line tool. You should set this to the root of - | the application so that it's available within Artisan commands. - | - */ - - 'url' => env('APP_URL', 'http://localhost'), - - /* - |-------------------------------------------------------------------------- - | Application Timezone - |-------------------------------------------------------------------------- - | - | Here you may specify the default timezone for your application, which - | will be used by the PHP date and date-time functions. The timezone - | is set to "UTC" by default as it is suitable for most use cases. - | - */ - - 'timezone' => env('APP_TIMEZONE', 'UTC'), - - /* - |-------------------------------------------------------------------------- - | Application Locale Configuration - |-------------------------------------------------------------------------- - | - | The application locale determines the default locale that will be used - | by Laravel's translation / localization methods. This option can be - | set to any locale for which you plan to have translation strings. - | - */ - - 'locale' => env('APP_LOCALE', 'en'), - - 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), - - 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), - - /* - |-------------------------------------------------------------------------- - | Encryption Key - |-------------------------------------------------------------------------- - | - | This key is utilized by Laravel's encryption services and should be set - | to a random, 32 character string to ensure that all encrypted values - | are secure. You should do this prior to deploying the application. - | - */ - - 'cipher' => 'AES-256-CBC', - - 'key' => env('APP_KEY'), - - 'previous_keys' => [ - ...array_filter( - explode(',', env('APP_PREVIOUS_KEYS', '')) - ), - ], - - /* - |-------------------------------------------------------------------------- - | Maintenance Mode Driver - |-------------------------------------------------------------------------- - | - | These configuration options determine the driver used to determine and - | manage Laravel's "maintenance mode" status. The "cache" driver will - | allow maintenance mode to be controlled across multiple machines. - | - | Supported drivers: "file", "cache" - | - */ - - 'maintenance' => [ - 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), - 'store' => env('APP_MAINTENANCE_STORE', 'database'), - ], - - /* - |-------------------------------------------------------------------------- - | Exception Reporter Configuration - |-------------------------------------------------------------------------- - | - | If you're encountering weird behavior with the Panel and no exceptions - | are being logged try changing the environment variable below to be true. - | This will override the default "don't report" behavior of the Panel and log - | all exceptions. This will be quite noisy. - | - */ - - 'exceptions' => [ - 'report_all' => env('APP_REPORT_ALL_EXCEPTIONS', false), - ], - - /* - |-------------------------------------------------------------------------- - | Autoloaded Service Providers - |-------------------------------------------------------------------------- - | - | The service providers listed here will be automatically loaded on the - | request to your application. Feel free to add your own services to - | this array to grant expanded functionality to your applications. - | - */ - - 'providers' => [ - /* - * Laravel Framework Service Providers... - */ - Illuminate\Auth\AuthServiceProvider::class, - Illuminate\Broadcasting\BroadcastServiceProvider::class, - Illuminate\Bus\BusServiceProvider::class, - Illuminate\Cache\CacheServiceProvider::class, - Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, - Illuminate\Cookie\CookieServiceProvider::class, - Illuminate\Database\DatabaseServiceProvider::class, - Illuminate\Encryption\EncryptionServiceProvider::class, - Illuminate\Filesystem\FilesystemServiceProvider::class, - Illuminate\Foundation\Providers\FoundationServiceProvider::class, - Illuminate\Hashing\HashServiceProvider::class, - Illuminate\Mail\MailServiceProvider::class, - Illuminate\Notifications\NotificationServiceProvider::class, - Illuminate\Pagination\PaginationServiceProvider::class, - Illuminate\Pipeline\PipelineServiceProvider::class, - Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, - Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, - Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, - Illuminate\View\ViewServiceProvider::class, - - /* - * Application Service Providers... - */ - Pterodactyl\Providers\ActivityLogServiceProvider::class, - Pterodactyl\Providers\AppServiceProvider::class, - Pterodactyl\Providers\AuthServiceProvider::class, - Pterodactyl\Providers\BackupsServiceProvider::class, - Pterodactyl\Providers\BladeServiceProvider::class, - Pterodactyl\Providers\EventServiceProvider::class, - Pterodactyl\Providers\HashidsServiceProvider::class, - Pterodactyl\Providers\RouteServiceProvider::class, - Pterodactyl\Providers\RepositoryServiceProvider::class, - Pterodactyl\Providers\ViewComposerServiceProvider::class, - - /* - * Additional Dependencies - */ - Prologue\Alerts\AlertsServiceProvider::class, - ], - - /* - |-------------------------------------------------------------------------- - | Class Aliases - |-------------------------------------------------------------------------- - | - | This array of class aliases will be registered when this application - | is started. However, feel free to register as many as you wish as - | the aliases are "lazy" loaded, so they don't hinder performance. - | - */ - - 'aliases' => Facade::defaultAliases()->merge([ - 'Alert' => Prologue\Alerts\Facades\Alert::class, - 'Carbon' => Carbon\Carbon::class, - 'JavaScript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, - 'Theme' => Pterodactyl\Extensions\Facades\Theme::class, - - // Custom Facades - 'Activity' => Pterodactyl\Facades\Activity::class, - 'LogBatch' => Pterodactyl\Facades\LogBatch::class, - 'LogTarget' => Pterodactyl\Facades\LogTarget::class, - ])->toArray(), + // Custom Facades + 'Activity' => Pterodactyl\Facades\Activity::class, + 'LogBatch' => Pterodactyl\Facades\LogBatch::class, + 'LogTarget' => Pterodactyl\Facades\LogTarget::class, + ])->toArray(), ]; diff --git a/config/backups.php b/config/backups.php index f466ea4d5..e20d0a469 100644 --- a/config/backups.php +++ b/config/backups.php @@ -21,16 +21,6 @@ return [ // to 6 hours. To disable this feature, set the value to `0`. 'prune_age' => env('BACKUP_PRUNE_AGE', 360), - // Defines the backup creation throttle limits for users. In this default example, we allow - // a user to create two (successful or pending) backups per 10 minutes. Even if they delete - // a backup it will be included in the throttle count. - // - // Set the period to "0" to disable this throttle. The period is defined in seconds. - 'throttles' => [ - 'limit' => env('BACKUP_THROTTLE_LIMIT', 2), - 'period' => env('BACKUP_THROTTLE_PERIOD', 600), - ], - 'disks' => [ // There is no configuration for the local disk for Wings. That configuration // is determined by the Daemon configuration, and not the Panel. diff --git a/config/captcha.php b/config/captcha.php new file mode 100644 index 000000000..b5e2fef12 --- /dev/null +++ b/config/captcha.php @@ -0,0 +1,63 @@ + env('CAPTCHA_DRIVER', 'none'), + + 'providers' => [ + 'hcaptcha' => [ + 'enabled' => env('HCAPTCHA_ENABLED', false), + 'site_key' => env('HCAPTCHA_SITE_KEY'), + 'secret_key' => env('HCAPTCHA_SECRET_KEY'), + 'endpoint' => 'https://hcaptcha.com/siteverify', + ], + + 'mcaptcha' => [ + 'enabled' => env('MCAPTCHA_ENABLED', false), + 'site_key' => env('MCAPTCHA_SITE_KEY'), + 'secret_key' => env('MCAPTCHA_SECRET_KEY'), + 'endpoint' => env('MCAPTCHA_ENDPOINT', 'https://mcaptcha.your-instance.com/api/v1/pow/verify'), + ], + + 'turnstile' => [ + 'enabled' => env('TURNSTILE_ENABLED', false), + 'site_key' => env('TURNSTILE_SITE_KEY'), + 'secret_key' => env('TURNSTILE_SECRET_KEY'), + 'endpoint' => 'https://challenges.cloudflare.com/turnstile/v0/siteverify', + 'theme' => env('TURNSTILE_THEME', 'auto'), // auto, light, dark + 'size' => env('TURNSTILE_SIZE', 'normal'), // normal, compact, flexible + 'action' => env('TURNSTILE_ACTION', null), // Optional action identifier + 'cdata' => env('TURNSTILE_CDATA', null), // Optional customer data + 'retry' => env('TURNSTILE_RETRY', 'auto'), // auto, never + 'retry_interval' => env('TURNSTILE_RETRY_INTERVAL', 8000), // milliseconds + 'refresh_expired' => env('TURNSTILE_REFRESH_EXPIRED', 'auto'), // auto, manual, never + 'refresh_timeout' => env('TURNSTILE_REFRESH_TIMEOUT', 'auto'), // auto, manual, never + 'appearance' => env('TURNSTILE_APPEARANCE', 'always'), // always, execute, interaction-only + 'execution' => env('TURNSTILE_EXECUTION', 'render'), // render, execute + ], + + 'proton' => [ + 'enabled' => env('PROTON_CAPTCHA_ENABLED', false), + 'site_key' => env('PROTON_CAPTCHA_SITE_KEY'), + 'secret_key' => env('PROTON_CAPTCHA_SECRET_KEY'), + 'endpoint' => 'https://api.proton.me/captcha/v3/verify', + ], + + 'friendly' => [ + 'enabled' => env('FRIENDLY_CAPTCHA_ENABLED', false), + 'site_key' => env('FRIENDLY_CAPTCHA_SITE_KEY'), + 'secret_key' => env('FRIENDLY_CAPTCHA_SECRET_KEY'), + 'endpoint' => 'https://api.friendlycaptcha.com/api/v1/siteverify', + ], + + 'recaptcha' => [ + 'enabled' => env('RECAPTCHA_ENABLED', false), + 'site_key' => env('RECAPTCHA_SITE_KEY'), + 'secret_key' => env('RECAPTCHA_SECRET_KEY'), + 'endpoint' => 'https://www.google.com/recaptcha/api/siteverify', + ], + ], + + // Global settings + 'verify_domain' => false, + 'timeout' => 5, // seconds +]; diff --git a/config/cors.php b/config/cors.php index bf72895e0..2a1eac838 100644 --- a/config/cors.php +++ b/config/cors.php @@ -1,57 +1,57 @@ ['/api/client', '/api/application', '/api/client/*', '/api/application/*'], + /* + * You can enable CORS for 1 or multiple paths. + * Example: ['api/*'] + */ + 'paths' => ['/api/client', '/api/application', '/api/client/*', '/api/application/*'], - /* - * Matches the request method. `['*']` allows all methods. - */ - 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'], + /* + * Matches the request method. `['*']` allows all methods. + */ + 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'], - /* - * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` - */ - 'allowed_origins' => explode(',', env('APP_CORS_ALLOWED_ORIGINS') ?? ''), + /* + * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` + */ + 'allowed_origins' => explode(',', env('APP_CORS_ALLOWED_ORIGINS') ?? ''), - /* - * Patterns that can be used with `preg_match` to match the origin. - */ - 'allowed_origins_patterns' => [], + /* + * Patterns that can be used with `preg_match` to match the origin. + */ + 'allowed_origins_patterns' => [], - /* - * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. - */ - 'allowed_headers' => ['*'], + /* + * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. + */ + 'allowed_headers' => ['*'], - /* - * Sets the Access-Control-Expose-Headers response header with these headers. - */ - 'exposed_headers' => [], + /* + * Sets the Access-Control-Expose-Headers response header with these headers. + */ + 'exposed_headers' => [], - /* - * Sets the Access-Control-Max-Age response header when > 0. - */ - 'max_age' => 0, + /* + * Sets the Access-Control-Max-Age response header when > 0. + */ + 'max_age' => 0, - /* - * Sets the Access-Control-Allow-Credentials header. - */ - 'supports_credentials' => true, + /* + * Sets the Access-Control-Allow-Credentials header. + */ + 'supports_credentials' => true, ]; diff --git a/config/database.php b/config/database.php index d82a6fb90..1fa31d610 100644 --- a/config/database.php +++ b/config/database.php @@ -48,7 +48,7 @@ return [ 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => env('DB_PREFIX', ''), 'prefix_indexes' => true, - 'strict' => env('DB_STRICT_MODE', false), // TODO: true by default + 'strict' => env('DB_STRICT_MODE', false), // TODO: fix various errors with strict mode enabled 'engine' => null, 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))), 'sslmode' => env('DB_SSLMODE', 'prefer'), @@ -73,7 +73,7 @@ return [ 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => env('DB_PREFIX', ''), 'prefix_indexes' => true, - 'strict' => env('DB_STRICT_MODE', true), + 'strict' => env('DB_STRICT_MODE', false), // TODO: fix various errors with strict mode enabled 'engine' => null, 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))), 'sslmode' => env('DB_SSLMODE', 'prefer'), diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 58c58bc9e..9eb17ee58 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -44,6 +44,35 @@ return [ ], ], + /* + |-------------------------------------------------------------------------- + | Captcha Configuration + |-------------------------------------------------------------------------- + | + | Configuration for captcha providers used in authentication forms. + */ + + 'captcha' => [ + 'provider' => env('CAPTCHA_PROVIDER', 'none'), + 'turnstile' => [ + 'site_key' => env('TURNSTILE_SITE_KEY', ''), + 'secret_key' => env('TURNSTILE_SECRET_KEY', ''), + ], + 'hcaptcha' => [ + 'site_key' => env('HCAPTCHA_SITE_KEY', ''), + 'secret_key' => env('HCAPTCHA_SECRET_KEY', ''), + ], + 'recaptcha' => [ + 'site_key' => env('RECAPTCHA_SITE_KEY', ''), + 'secret_key' => env('RECAPTCHA_SECRET_KEY', ''), + ], + 'forms' => [ + 'login' => env('CAPTCHA_LOGIN', false), + 'forgot_password' => env('CAPTCHA_FORGOT_PASSWORD', false), + 'reset_password' => env('CAPTCHA_RESET_PASSWORD', false), + ], + ], + /* |-------------------------------------------------------------------------- | Pagination diff --git a/config/recaptcha.php b/config/recaptcha.php deleted file mode 100644 index 757e184a5..000000000 --- a/config/recaptcha.php +++ /dev/null @@ -1,31 +0,0 @@ - env('RECAPTCHA_ENABLED', true), - - /* - * API endpoint for recaptcha checks. You should not edit this. - */ - 'domain' => env('RECAPTCHA_DOMAIN', 'https://www.google.com/recaptcha/api/siteverify'), - - /* - * Use a custom secret key, we use our public one by default - */ - 'secret_key' => env('RECAPTCHA_SECRET_KEY', '6LcJcjwUAAAAALOcDJqAEYKTDhwELCkzUkNDQ0J5'), - '_shipped_secret_key' => '6LcJcjwUAAAAALOcDJqAEYKTDhwELCkzUkNDQ0J5', - - /* - * Use a custom website key, we use our public one by default - */ - 'website_key' => env('RECAPTCHA_WEBSITE_KEY', '6LcJcjwUAAAAAO_Xqjrtj9wWufUpYRnK6BW8lnfn'), - '_shipped_website_key' => '6LcJcjwUAAAAAO_Xqjrtj9wWufUpYRnK6BW8lnfn', - - /* - * Domain verification is enabled by default and compares the domain used when solving the captcha - * as public keys can't have domain verification on google's side enabled (obviously). - */ - 'verify_domain' => true, -]; diff --git a/config/server_operations.php b/config/server_operations.php new file mode 100644 index 000000000..ef7a80458 --- /dev/null +++ b/config/server_operations.php @@ -0,0 +1,41 @@ + [ + 'egg_change' => 1800, // 30 minutes + 'reinstall' => 1200, // 20 minutes + 'backup_restore' => 2400, // 40 minutes + 'default' => 900, // 15 minutes + ], + + /* + |-------------------------------------------------------------------------- + | Operation Cleanup + |-------------------------------------------------------------------------- + | + | Configuration for automatic cleanup of old completed operations + | to prevent database bloat and maintain performance. + | + */ + 'cleanup' => [ + 'enabled' => true, // Enable automatic cleanup + 'retain_days' => 30, // Days to retain completed operations + 'chunk_size' => 100, // Records to process per cleanup batch + ], +]; \ No newline at end of file diff --git a/config/turnstile.php b/config/turnstile.php deleted file mode 100644 index 4754311c2..000000000 --- a/config/turnstile.php +++ /dev/null @@ -1,31 +0,0 @@ - env('TURNSTILE_ENABLED', false), - - /* - * API endpoint for Turnstile checks. - * The endpoint is specific to Turnstile verification. - */ - 'domain' => env('TURNSTILE_DOMAIN', 'https://challenges.cloudflare.com/turnstile/v0/siteverify'), - - /* - * Use a custom secret key for Turnstile. Replace with your Turnstile secret key. - */ - 'secret_key' => env('TURNSTILE_SECRET_KEY', 'your-turnstile-secret-key'), - - /* - * Use a custom website key for Turnstile. Replace with your Turnstile website key. - */ - 'website_key' => env('TURNSTILE_WEBSITE_KEY', 'your-turnstile-website-key'), - - /* - * Turnstile doesn't require domain verification, but if you want to implement domain validation, you can. - * Set this to false if you don't want to do domain verification. - */ - 'verify_domain' => false, // You can change this based on your needs -]; - diff --git a/database/Seeders/eggs/minecraft/egg-bungeecord.json b/database/Seeders/eggs/minecraft/egg-bungeecord.json index 0ecf03ac2..f654f886e 100644 --- a/database/Seeders/eggs/minecraft/egg-bungeecord.json +++ b/database/Seeders/eggs/minecraft/egg-bungeecord.json @@ -11,7 +11,10 @@ "features": [ "eula", "java_version", - "pid_limit" + "pid_limit", + "plugin/bungeecord", + "mclogs", + "subdomain_minecraft" ], "docker_images": { "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", diff --git a/database/Seeders/eggs/minecraft/egg-fabric.json b/database/Seeders/eggs/minecraft/egg-fabric.json index f88d75a2b..8f6f14d27 100644 --- a/database/Seeders/eggs/minecraft/egg-fabric.json +++ b/database/Seeders/eggs/minecraft/egg-fabric.json @@ -11,7 +11,10 @@ "features": [ "eula", "java_version", - "pid_limit" + "pid_limit", + "mod/fabric", + "mclogs", + "subdomain_minecraft" ], "docker_images": { "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index a30a3087c..3efd54127 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -11,7 +11,10 @@ "features": [ "eula", "java_version", - "pid_limit" + "pid_limit", + "mod/forge", + "mclogs", + "subdomain_minecraft" ], "docker_images": { "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", diff --git a/database/Seeders/eggs/minecraft/egg-paper.json b/database/Seeders/eggs/minecraft/egg-paper.json index fd05af6f1..71768a903 100644 --- a/database/Seeders/eggs/minecraft/egg-paper.json +++ b/database/Seeders/eggs/minecraft/egg-paper.json @@ -11,7 +11,10 @@ "features": [ "eula", "java_version", - "pid_limit" + "pid_limit", + "plugin/paper", + "mclogs", + "subdomain_minecraft" ], "docker_images": { "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", diff --git a/database/Seeders/eggs/minecraft/egg-purpur.json b/database/Seeders/eggs/minecraft/egg-purpur.json index 799e7c172..f561d38b7 100644 --- a/database/Seeders/eggs/minecraft/egg-purpur.json +++ b/database/Seeders/eggs/minecraft/egg-purpur.json @@ -11,7 +11,10 @@ "features": [ "eula", "java_version", - "pid_limit" + "pid_limit", + "plugin/purpur", + "mclogs", + "subdomain_minecraft" ], "docker_images": { "Java 21": "ghcr.io/pterodactyl/yolks:java_21", @@ -22,7 +25,7 @@ "Java 8": "ghcr.io/pterodactyl/yolks:java_8" }, "file_denylist": [], - "startup": "java --add-modules=jdk.incubator.vector -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", + "startup": "java --add-modules=jdk.incubator.vector -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", "config": { "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", "logs": "{}", diff --git a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json index 7a4c782b4..5735ac363 100644 --- a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json +++ b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json @@ -11,7 +11,10 @@ "features": [ "eula", "java_version", - "pid_limit" + "pid_limit", + "plugin/sponge", + "mclogs", + "subdomain_minecraft" ], "docker_images": { "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", diff --git a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json index 71fd16547..19db07133 100644 --- a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json @@ -11,7 +11,10 @@ "features": [ "eula", "java_version", - "pid_limit" + "pid_limit", + "vanilla", + "mclogs", + "subdomain_minecraft" ], "docker_images": { "Java 21": "ghcr.io\/pterodactyl\/yolks:java_21", diff --git a/database/Seeders/eggs/rust/egg-rust.json b/database/Seeders/eggs/rust/egg-rust.json index 35f543c75..2e0631ccf 100644 --- a/database/Seeders/eggs/rust/egg-rust.json +++ b/database/Seeders/eggs/rust/egg-rust.json @@ -9,7 +9,8 @@ "author": "support@pterodactyl.io", "description": "The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.", "features": [ - "steam_disk_space" + "steam_disk_space", + "subdomain_rust" ], "docker_images": { "ghcr.io\/pterodactyl\/games:rust": "ghcr.io\/pterodactyl\/games:rust" diff --git a/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json b/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json index 01f7360bc..626d7e6a5 100644 --- a/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json +++ b/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json @@ -8,7 +8,9 @@ "name": "Teamspeak3 Server", "author": "support@pterodactyl.io", "description": "VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.", - "features": null, + "features": [ + "subdomain_teamspeak" + ], "docker_images": { "ghcr.io\/pterodactyl\/yolks:debian": "ghcr.io\/pterodactyl\/yolks:debian" }, diff --git a/database/migrations/2025_05_25_230411_add_internal_fqdn_to_nodes_table.php b/database/migrations/2025_05_25_230411_add_internal_fqdn_to_nodes_table.php new file mode 100644 index 000000000..c3dea242e --- /dev/null +++ b/database/migrations/2025_05_25_230411_add_internal_fqdn_to_nodes_table.php @@ -0,0 +1,28 @@ +string('internal_fqdn')->nullable()->after('fqdn'); + $table->boolean('use_separate_fqdns')->default(false)->after('internal_fqdn'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropColumn(['internal_fqdn', 'use_separate_fqdns']); + }); + } +}; diff --git a/database/migrations/2025_06_11_add_is_processing_to_tasks.php b/database/migrations/2025_06_11_add_is_processing_to_tasks.php new file mode 100644 index 000000000..aea450e9e --- /dev/null +++ b/database/migrations/2025_06_11_add_is_processing_to_tasks.php @@ -0,0 +1,21 @@ +boolean('is_processing')->default(false)->after('is_queued'); + }); + } + + public function down(): void + { + Schema::table('tasks', function (Blueprint $table) { + $table->dropColumn('is_processing'); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2025_08_06_192246_add_overhead_memory_to_servers_table.php b/database/migrations/2025_08_06_192246_add_overhead_memory_to_servers_table.php new file mode 100644 index 000000000..54805324d --- /dev/null +++ b/database/migrations/2025_08_06_192246_add_overhead_memory_to_servers_table.php @@ -0,0 +1,28 @@ +integer('overhead_memory')->unsigned()->default(0)->after('memory'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('overhead_memory'); + }); + } +} \ No newline at end of file diff --git a/database/migrations/2025_08_14_025040_create_server_operations_table.php b/database/migrations/2025_08_14_025040_create_server_operations_table.php new file mode 100644 index 000000000..1fd6c8227 --- /dev/null +++ b/database/migrations/2025_08_14_025040_create_server_operations_table.php @@ -0,0 +1,52 @@ +id(); + $table->string('operation_id', 36)->unique(); + $table->unsignedInteger('server_id'); + $table->unsignedInteger('user_id'); + $table->string('type', 50); + $table->string('status', 20)->default('pending'); + $table->text('message')->nullable(); + $table->json('parameters')->nullable(); + $table->timestamp('started_at')->nullable(); + $table->timestamps(); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + + $table->index(['server_id', 'status', 'created_at'], 'server_operations_server_status_created'); + $table->index(['type', 'status', 'created_at'], 'server_operations_type_status_created'); + $table->index(['status', 'created_at'], 'server_operations_status_created'); + $table->index(['server_id', 'status'], 'server_operations_server_status'); + $table->index(['status', 'started_at'], 'server_operations_status_started'); + $table->index(['user_id', 'type', 'created_at'], 'server_operations_user_type_created'); + $table->index(['operation_id', 'server_id'], 'server_operations_operation_server'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('server_operations'); + } +}; \ No newline at end of file diff --git a/database/migrations/2025_08_16_000000_add_server_state_to_backups.php b/database/migrations/2025_08_16_000000_add_server_state_to_backups.php new file mode 100644 index 000000000..4bbf75937 --- /dev/null +++ b/database/migrations/2025_08_16_000000_add_server_state_to_backups.php @@ -0,0 +1,29 @@ +json('server_state')->nullable()->after('ignored_files') + ->comment('Captured server state at backup time including nest_id, egg_id, startup command, image, and variables'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('backups', function (Blueprint $table) { + $table->dropColumn('server_state'); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2025_08_17_225800_add_exclude_from_resource_calculation_to_servers_table.php b/database/migrations/2025_08_17_225800_add_exclude_from_resource_calculation_to_servers_table.php new file mode 100644 index 000000000..00f22679b --- /dev/null +++ b/database/migrations/2025_08_17_225800_add_exclude_from_resource_calculation_to_servers_table.php @@ -0,0 +1,28 @@ +boolean('exclude_from_resource_calculation')->default(false)->after('oom_disabled'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('exclude_from_resource_calculation'); + }); + } +} \ No newline at end of file diff --git a/database/migrations/2025_09_07_000001_create_subdomain_system.php b/database/migrations/2025_09_07_000001_create_subdomain_system.php new file mode 100644 index 000000000..a60df3616 --- /dev/null +++ b/database/migrations/2025_09_07_000001_create_subdomain_system.php @@ -0,0 +1,60 @@ +id(); + $table->string('name')->unique(); // example.com + $table->string('dns_provider'); // cloudflare, route53, etc. + $table->json('dns_config'); // provider-specific configuration + $table->boolean('is_active')->default(true); + $table->boolean('is_default')->default(false); + $table->timestamps(); + + $table->index(['is_active']); + $table->index(['is_default']); + }); + + // Create server_subdomains table + Schema::create('server_subdomains', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('server_id'); // Match servers table increments('id') + $table->unsignedBigInteger('domain_id'); // Match domains table id() + $table->string('subdomain'); // myserver + $table->string('record_type'); // A, SRV, CNAME, etc. + $table->json('dns_records'); // Array of DNS record IDs from the provider + $table->boolean('is_active')->default(true); + $table->timestamps(); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + $table->foreign('domain_id')->references('id')->on('domains')->onDelete('cascade'); + + // Ensure unique subdomain per domain + $table->unique(['domain_id', 'subdomain']); + $table->unique(['server_id', 'is_active'], 'server_subdomains_server_active_unique'); + + $table->index(['server_id']); + $table->index(['is_active']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('server_subdomains'); + Schema::dropIfExists('domains'); + } +}; \ No newline at end of file diff --git a/docker-compose.develop.yml b/docker-compose.develop.yml new file mode 100644 index 000000000..8df2594aa --- /dev/null +++ b/docker-compose.develop.yml @@ -0,0 +1,191 @@ +x-common: + database: &db-environment + # Do not remove the "&db-password" from the end of the line below, it is important + # for Panel functionality. + MYSQL_PASSWORD: &db-password 'password' + MYSQL_ROOT_PASSWORD: &db-root-password 'rootpassword' + panel: &panel-environment + APP_URL: 'http://panel' + # A list of valid timezones can be found here: http://php.net/manual/en/timezones.php + APP_TIMEZONE: 'UTC' + APP_SERVICE_AUTHOR: 'noreply@example.com' + DB_USERNAME: 'pterodactyl' + # DB_PASSWORD: "Uncomment this to user your own password" + + # Uncomment the line below and set to a non-empty value if you want to use Let's Encrypt + # to generate an SSL certificate for the Panel. + # LE_EMAIL: "" + + mail: &mail-environment + MAIL_FROM: 'noreply@example.com' + MAIL_DRIVER: 'smtp' + MAIL_HOST: 'mailpit' + MAIL_PORT: '1025' + MAIL_USERNAME: '' + MAIL_PASSWORD: '' + MAIL_ENCRYPTION: 'true' + +# +# ------------------------------------------------------------------------------------------ +# DANGER ZONE BELOW +# +# The remainder of this file likely does not need to be changed. Please only make modifications +# below if you understand what you are doing. +# +services: + database: + image: mariadb:10.5 + restart: always + command: --default-authentication-plugin=mysql_native_password + volumes: + - './srv/database:/var/lib/mysql' + environment: + <<: *db-environment + MYSQL_DATABASE: 'panel' + MYSQL_USER: 'pterodactyl' + ports: + - '3306:3306' + cache: + image: redis:alpine + restart: always + panel: + image: pyrodactyl:develop + restart: always + ports: + - '3000:80' + # - '443:443' + links: + - database + - cache + volumes: + - '.:/app' + - './srv/var:/app/var' + - './srv/nginx/:/etc/nginx/http.d/' + - './srv/certs:/etc/letsencrypt' + - './srv/logs/:/app/storage/logs' + - './srv/pterodactyl/config/:/etc/pterodactyl' + # anonymous volumes below, meaning it'll use the image's directory instead of the host + - '/app/vendor' + # needed for linux permissions + # - '/app/storage' + # - '/app/storage/app/private' + - '/app/storage/framework/views' + - '/app/bootstrap/cache' + environment: + <<: [*panel-environment, *mail-environment] + DB_PASSWORD: *db-password + DB_ROOT_PASSWORD: *db-root-password + APP_ENV: 'local' + APP_DEBUG: 'true' + LOG_CHANNEL: 'stack' + LOG_LEVEL: 'debug' + APP_ENVIRONMENT_ONLY: 'false' + CACHE_DRIVER: 'redis' + SESSION_DRIVER: 'redis' + QUEUE_DRIVER: 'redis' + REDIS_HOST: 'cache' + DB_CONNECTION: 'mariadb' + DB_HOST: 'database' + DB_PORT: '3306' + HASHIDS_LENGTH: 8 + WINGS_INTERNAL_IP: 'wings' + WINGS_DIR: '${PWD}/srv/' + PYRODACTYL_DOCKER_DEV: 'true' + MAIL_HOST: 'mailhog' + MAIL_PORT: 1025 + MAIL_DRIVER: 'smtp' + APP_BACKUP_DRIVER: 's3' + AWS_DEFAULT_REGION: 'us-west-2' + AWS_ACCESS_KEY_ID: 'minioadmin' + AWS_SECRET_ACCESS_KEY: 'minioadmin' + AWS_BACKUPS_BUCKET: 'pyrodactyl' + AWS_ENDPOINT: 'http://minio:9000' + AWS_USE_PATH_STYLE_ENDPOINT: 'true' + + minio: + image: quay.io/minio/minio:latest + hostname: minio + expose: + - '9000' + - '9001' + volumes: + - ./srv/data1-1:/data1 + - ./srv/data1-2:/data2 + command: server --console-address ":9001" http://minio/data{1...2} + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + + createbuckets: + image: quay.io/minio/mc:latest + depends_on: + - minio + entrypoint: > + /bin/sh -c " + until mc alias set local http://minio:9000 minioadmin minioadmin; do + echo 'Waiting for MinIO to be ready...' + sleep 2 + done && + mc mb --ignore-existing local/pyrodactyl && + echo 'Bucket created.' + " + + wings: + # The default Wings image doesn't work on macOS Docker + # This fork simply removes the incompatible `io.priority` cgroup v2 flag + # For Linux users, you can use the default image by uncommenting the line below + # image: ghcr.io/pterodactyl/wings:latest + image: ghcr.io/he3als/wings-mac:latest + restart: always + ports: + - '8080:8080' + - '2022:2022' + tty: true + environment: + TZ: 'UTC' + WINGS_UID: 988 + WINGS_GID: 988 + WINGS_USERNAME: pterodactyl + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' + - '/etc/ssl/certs:/etc/ssl/certs:ro' + - './srv/pterodactyl/config/:/etc/pterodactyl/' + + # The volumes below need to be the exact same path in the container as on the host. + # + # The paths are currently hardcoded in the container on first run, meaning if you move + # this repo on your host, you'll need to delete "srv" folder so the paths can be recreated. + # + # If you change these from $PWD, make sure to update `WINGS_DIR` in the panel service too. + # Do not change anything but the $PWD part as this is also hardcoded in the container. + + - './srv/wings/tmp/:${PWD}/srv/wings/tmp/' + - './srv/wings/docker/containers/:${PWD}/srv/wings/docker/containers/' + - './srv/wings/:${PWD}/srv/wings/' + - './wings/logs/:${PWD}/srv/wings/logs/' + + + mailpit: + image: axllent/mailpit + volumes: + - ./data/mailpit/:/data + ports: + - '1025:1025' + - '8025:8025' + environment: + MP_MAX_MESSAGES: ${MP_MAX_MESSAGES:-5000} + MP_DATABASE: ${MP_DATABASE:-/data/mailpit.db} + MP_SMTP_AUTH_ACCEPT_ANY: ${MP_SMTP_AUTH_ACCEPT_ANY:-1} + MP_SMTP_AUTH_ALLOW_INSECURE: ${MP_SMTP_AUTH_ALLOW_INSECURE:-1} + +networks: + default: + ipam: + config: + - subnet: 172.20.0.0/16 + +volumes: + panel_vendor: + panel_storage: + panel_bootstrap_cache: + mailpit_data: diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 541746666..33314243f 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -47,7 +47,7 @@ services: image: redis:alpine restart: always panel: - image: ghcr.io/pyrohost/pyrodactyl:main + image: ghcr.io/pyrohost/pyrodactyl:latest restart: always ports: - '80:80' diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..5b276f61b --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,72 @@ +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import prettier from 'eslint-plugin-prettier'; +import pluginReact from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import turbo from 'eslint-plugin-turbo'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import { fileURLToPath } from 'node:url'; +import tsEslint from 'typescript-eslint'; + +const gitignorePath = fileURLToPath(new URL('.gitignore', import.meta.url)); + +export default defineConfig([ + includeIgnoreFile(gitignorePath), + { + ignores: ['public/**'], + }, + { + files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + plugins: { js }, + extends: ['js/recommended'], + languageOptions: { globals: { ...globals.browser } }, + }, + ...tsEslint.configs.recommended, + pluginReact.configs.flat.recommended, + eslintConfigPrettier, + { + plugins: { + 'react-hooks': reactHooks, + turbo, + prettier, + }, + settings: { react: { version: 'detect' } }, + rules: { + // React + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + + // React Hooks + ...reactHooks.configs.recommended.rules, + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + + // TypeScript + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + + // Prettier + 'prettier/prettier': 'error', + + // Turbo + 'turbo/no-undeclared-env-vars': 'error', + }, + }, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: './', + }, + }, + }, +]); diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index fca5a8aba..000000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,112 +0,0 @@ -import react from "eslint-plugin-react"; -import reactHooks from "eslint-plugin-react-hooks"; -import prettier from "eslint-plugin-prettier"; -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import { fixupPluginRules } from "@eslint/compat"; -import globals from "globals"; -import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all -}); - -export default [{ - ignores: [ - "**/public", - "**/node_modules", - "resources/views", - "**/babel.config.js", - "**/tailwind.config.js", - "**/webpack.config.js", - "**/tsconfig.json", - "**/.eslintrc.js", - ], -}, ...compat.extends( - "eslint:recommended", - "plugin:react/recommended", - "plugin:react/jsx-runtime", - "plugin:@typescript-eslint/recommended", -), { - plugins: { - react, - "react-hooks": fixupPluginRules(reactHooks), - prettier, - "@typescript-eslint": typescriptEslint, - }, - - languageOptions: { - globals: { - ...globals.browser, - ...globals.node, - }, - - parser: tsParser, - }, - - settings: { - react: { - pragma: "React", - version: "detect", - }, - - linkComponents: [{ - name: "Link", - linkAttribute: "to", - }, { - name: "NavLink", - linkAttribute: "to", - }], - }, - - rules: { - "@typescript-eslint/no-var-requires": 0, - "@typescript-eslint/ban-ts-comment": 0, - - "prettier/prettier": ["warn", { - endOfLine: "auto", - }, { - usePrettierrc: true, - }], - - "react/prop-types": 0, - "react/display-name": 0, - - "react/no-unknown-property": ["error", { - ignore: ["css"], - }], - - "@typescript-eslint/no-explicit-any": 0, - "@typescript-eslint/no-non-null-assertion": 0, - "no-use-before-define": 0, - "@typescript-eslint/no-use-before-define": "warn", - - "@typescript-eslint/no-unused-vars": ["warn", { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - }], - }, -}, { - files: ["**/*.ts", "**/*.tsx"], - - languageOptions: { - ecmaVersion: 6, - sourceType: "script", - - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - - project: "./tsconfig.json", - tsconfigRootDir: "./", - }, - }, -}]; \ No newline at end of file diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 5ba6ab84e..000000000 --- a/flake.lock +++ /dev/null @@ -1,61 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1732014248, - "narHash": "sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "23e89b7da85c3640bbc2173fe04f4bd114342367", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index b41b29980..000000000 --- a/flake.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - description = "Pterodactyl development environment"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - }; - - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; }; - - # Setup script - setupScript = pkgs.writeShellScriptBin "pterodactyl-setup" '' - ${builtins.readFile ./nix/buildsteps.sh} - - ''; - in { - # Development shell - devShell = pkgs.mkShell { - buildInputs = with pkgs; [ - php82 - php82Packages.composer - nodejs_20 - redis - mariadb - caddy - git - docker - docker-compose - tmux - setupScript - ]; - LC_ALL = "C.UTF-8"; - LANG = "C.UTF-8"; - shellHook = '' - redis-server --daemonize yes - - echo "Pterodactyl development environment ready." - echo "Run 'pterodactyl-setup' to initialize development environment" - ''; - }; - - # Default package (runs setup) - defaultPackage = setupScript; - } - ); -} diff --git a/nix/buildsteps.sh b/nix/buildsteps.sh index 8fed27684..7e30af441 100755 --- a/nix/buildsteps.sh +++ b/nix/buildsteps.sh @@ -123,7 +123,7 @@ main() { # Optional: Start development services tmux new-session -s pterodactyl-dev -d - tmux send-keys -t pterodactyl-dev 'npm run dev' C-m + tmux send-keys -t pterodactyl-dev 'pnpm run dev' C-m tmux new-window -t pterodactyl-dev tmux send-keys -t pterodactyl-dev 'php artisan serve' C-m tmux new-window -t pterodactyl-dev diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e4b656519..000000000 --- a/package-lock.json +++ /dev/null @@ -1,10978 +0,0 @@ -{ - "name": "pyrodactyl", - "version": "4.0.0-dev", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "pyrodactyl", - "version": "4.0.0-dev", - "dependencies": { - "@codemirror/autocomplete": "^6.16.0", - "@codemirror/commands": "^6.3.3", - "@codemirror/language-data": "^6.5.1", - "@codemirror/legacy-modes": "^6.4.0", - "@codemirror/lint": "^6.8.2", - "@codemirror/search": "^6.5.7", - "@codemirror/state": "^6.4.1", - "@codemirror/view": "^6.34.3", - "@eslint/compat": "^1.2.2", - "@fortawesome/fontawesome-svg-core": "^6.6.0", - "@fortawesome/free-solid-svg-icons": "^6.6.0", - "@fortawesome/react-fontawesome": "^0.2.2", - "@headlessui/react": "^1.7.18", - "@preact/signals-react": "^2.0.1", - "@radix-ui/react-checkbox": "^1.0.4", - "@radix-ui/react-context-menu": "^2.1.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-switch": "^1.0.3", - "@radix-ui/react-tabs": "^1.0.4", - "@sentry/react": "^7.120.0", - "@sentry/vite-plugin": "^2.16.1", - "@tanstack/react-virtual": "^3.2.1", - "@xterm/addon-fit": "^0.10.0", - "@xterm/addon-search": "^0.15.0", - "@xterm/addon-web-links": "^0.11.0", - "@xterm/xterm": "^5.5.0", - "axios": "^1.7.7", - "browserslist": "^4.23.0", - "chart.js": "^4.4.4", - "clsx": "^2.1.0", - "cmdk": "^1.0.0", - "copy-to-clipboard": "^3.3.3", - "date-fns": "^3.6.0", - "debounce": "^2.2.0", - "deepmerge-ts": "^7.1.0", - "easy-peasy": "^6.0.4", - "events": "^3.3.0", - "formik": "^2.4.5", - "framer-motion": "^11.11.11", - "globals": "^15.9.0", - "i18next-http-backend": "^3.0.2", - "laravel-vite-plugin": "^1.0.2", - "million": "^3.0.6", - "pathe": "^1.1.2", - "qrcode.react": "^3.1.0", - "react": "^18.3.1", - "react-chartjs-2": "^5.2.0", - "react-dom": "^18.3.0", - "react-fast-compare": "^3.2.2", - "react-router-dom": "^6.22.3", - "reaptcha": "^1.12.1", - "sockette": "^2.0.6", - "sonner": "^1.4.41", - "styled-components": "^6.1.17", - "swr": "^2.2.5", - "tailwind-merge": "^2.2.2", - "tailwind-scrollbar": "^3.1.0", - "tailwindcss": "^3.4.10", - "tailwindcss-animate": "^1.0.7", - "turbo": "^2.0.14", - "twin.macro": "^3.4.1", - "uuid": "^9.0.1", - "vite": "^5.4.11", - "yup": "^1.4.0" - }, - "devDependencies": { - "@swc/core": "^1.7.10", - "@swc/plugin-styled-components": "^2.0.11", - "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@types/codemirror": "^5.60.15", - "@types/debounce": "^1.2.4", - "@types/events": "^3.0.3", - "@types/node": "^20.12.7", - "@types/qrcode.react": "^1.0.5", - "@types/react": "^18.3.1", - "@types/react-copy-to-clipboard": "^5.0.7", - "@types/react-dom": "^18.3.0", - "@types/react-redux": "^7.1.33", - "@types/uuid": "^9.0.8", - "@typescript-eslint/eslint-plugin": "^7.5.0", - "@typescript-eslint/parser": "^7.5.0", - "@vitejs/plugin-react-swc": "^3.6.0", - "autoprefixer": "^10.4.19", - "eslint": "^9.15.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", - "i18next": "^25.0.2", - "i18next-browser-languagedetector": "^8.0.5", - "postcss": "^8.4.38", - "postcss-import": "^16.1.0", - "postcss-nesting": "^12.1.1", - "postcss-preset-env": "^9.5.5", - "prettier": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.5.13", - "react-i18next": "^15.5.1", - "tailwindcss-inner-border": "^0.2.0", - "ts-essentials": "^9.4.2", - "typescript": "^5.4.5", - "vite-plugin-manifest-sri": "^0.2.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", - "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", - "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", - "dependencies": { - "@babel/types": "^7.26.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@codemirror/autocomplete": { - "version": "6.18.3", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz", - "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0" - }, - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/commands": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz", - "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.4.0", - "@codemirror/view": "^6.27.0", - "@lezer/common": "^1.1.0" - } - }, - "node_modules/@codemirror/lang-angular": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.3.tgz", - "integrity": "sha512-xgeWGJQQl1LyStvndWtruUvb4SnBZDAu/gvFH/ZU+c0W25tQR8e5hq7WTwiIY2dNxnf+49mRiGI/9yxIwB6f5w==", - "dependencies": { - "@codemirror/lang-html": "^6.0.0", - "@codemirror/lang-javascript": "^6.1.2", - "@codemirror/language": "^6.0.0", - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.3.3" - } - }, - "node_modules/@codemirror/lang-cpp": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz", - "integrity": "sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@lezer/cpp": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-css": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.0.tgz", - "integrity": "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.2", - "@lezer/css": "^1.1.7" - } - }, - "node_modules/@codemirror/lang-go": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", - "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.6.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/go": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-html": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", - "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/lang-css": "^6.0.0", - "@codemirror/lang-javascript": "^6.0.0", - "@codemirror/language": "^6.4.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0", - "@lezer/css": "^1.1.0", - "@lezer/html": "^1.3.0" - } - }, - "node_modules/@codemirror/lang-java": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.1.tgz", - "integrity": "sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@lezer/java": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-javascript": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz", - "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.6.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0", - "@lezer/javascript": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-json": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", - "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@lezer/json": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-less": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz", - "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==", - "dependencies": { - "@codemirror/lang-css": "^6.2.0", - "@codemirror/language": "^6.0.0", - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-liquid": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.2.2.tgz", - "integrity": "sha512-7Dm841fk37+JQW6j2rI1/uGkJyESrjzyhiIkaLjbbR0U6aFFQvMrJn35WxQreRMADMhzkyVkZM4467OR7GR8nQ==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/lang-html": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.3.1" - } - }, - "node_modules/@codemirror/lang-markdown": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.1.tgz", - "integrity": "sha512-y3sSPuQjBKZQbQwe3ZJKrSW6Silyl9PnrU/Mf0m2OQgIlPoSYTtOvEL7xs94SVMkb8f4x+SQFnzXPdX4Wk2lsg==", - "dependencies": { - "@codemirror/autocomplete": "^6.7.1", - "@codemirror/lang-html": "^6.0.0", - "@codemirror/language": "^6.3.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.2.1", - "@lezer/markdown": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-php": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.1.tgz", - "integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==", - "dependencies": { - "@codemirror/lang-html": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/php": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-python": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.1.6.tgz", - "integrity": "sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==", - "dependencies": { - "@codemirror/autocomplete": "^6.3.2", - "@codemirror/language": "^6.8.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.2.1", - "@lezer/python": "^1.1.4" - } - }, - "node_modules/@codemirror/lang-rust": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz", - "integrity": "sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@lezer/rust": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-sass": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz", - "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==", - "dependencies": { - "@codemirror/lang-css": "^6.2.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.2", - "@lezer/sass": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-sql": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.8.0.tgz", - "integrity": "sha512-aGLmY4OwGqN3TdSx3h6QeA1NrvaYtF7kkoWR/+W7/JzB0gQtJ+VJxewlnE3+VImhA4WVlhmkJr109PefOOhjLg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-vue": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz", - "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==", - "dependencies": { - "@codemirror/lang-html": "^6.0.0", - "@codemirror/lang-javascript": "^6.1.2", - "@codemirror/language": "^6.0.0", - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.3.1" - } - }, - "node_modules/@codemirror/lang-wast": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz", - "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-xml": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", - "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.4.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/xml": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-yaml": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.1.tgz", - "integrity": "sha512-HV2NzbK9bbVnjWxwObuZh5FuPCowx51mEfoFT9y3y+M37fA3+pbxx4I7uePuygFzDsAmCTwQSc/kXh/flab4uw==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.2.0", - "@lezer/yaml": "^1.0.0" - } - }, - "node_modules/@codemirror/language": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.4.tgz", - "integrity": "sha512-qjt7Wn/nxGuI278GYVlqE5V93Xn8ZQwzqZtgS0FaWr7K2yWgd5/FlBNqNi4jtUvBVvWJzAGfnggIlpyjTOaF4A==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.1.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" - } - }, - "node_modules/@codemirror/language-data": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.1.tgz", - "integrity": "sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==", - "dependencies": { - "@codemirror/lang-angular": "^0.1.0", - "@codemirror/lang-cpp": "^6.0.0", - "@codemirror/lang-css": "^6.0.0", - "@codemirror/lang-go": "^6.0.0", - "@codemirror/lang-html": "^6.0.0", - "@codemirror/lang-java": "^6.0.0", - "@codemirror/lang-javascript": "^6.0.0", - "@codemirror/lang-json": "^6.0.0", - "@codemirror/lang-less": "^6.0.0", - "@codemirror/lang-liquid": "^6.0.0", - "@codemirror/lang-markdown": "^6.0.0", - "@codemirror/lang-php": "^6.0.0", - "@codemirror/lang-python": "^6.0.0", - "@codemirror/lang-rust": "^6.0.0", - "@codemirror/lang-sass": "^6.0.0", - "@codemirror/lang-sql": "^6.0.0", - "@codemirror/lang-vue": "^0.1.1", - "@codemirror/lang-wast": "^6.0.0", - "@codemirror/lang-xml": "^6.0.0", - "@codemirror/lang-yaml": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/legacy-modes": "^6.4.0" - } - }, - "node_modules/@codemirror/legacy-modes": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.4.2.tgz", - "integrity": "sha512-HsvWu08gOIIk303eZQCal4H4t65O/qp1V4ul4zVa3MHK5FJ0gz3qz3O55FIkm+aQUcshUOjBx38t2hPiJwW5/g==", - "dependencies": { - "@codemirror/language": "^6.0.0" - } - }, - "node_modules/@codemirror/lint": { - "version": "6.8.3", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.3.tgz", - "integrity": "sha512-GSGfKxCo867P7EX1k2LoCrjuQFeqVgPGRRsSl4J4c0KMkD+k1y6WYvTQkzv0iZ8JhLJDujEvlnMchv4CZQLh3Q==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.35.0", - "crelt": "^1.0.5" - } - }, - "node_modules/@codemirror/search": { - "version": "6.5.8", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz", - "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "node_modules/@codemirror/state": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", - "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" - }, - "node_modules/@codemirror/view": { - "version": "6.35.0", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.0.tgz", - "integrity": "sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==", - "dependencies": { - "@codemirror/state": "^6.4.0", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" - } - }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.13.tgz", - "integrity": "sha512-MX0yLTwtZzr82sQ0zOjqimpZbzjMaK/h2pmlrLK7DCzlmiZLYFpoO94WmN1akRVo6ll/TdpHb53vihHLUMyvng==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.2.1.tgz", - "integrity": "sha512-CEypeeykO9AN7JWkr1OEOQb0HRzZlPWGwV0Ya6DuVgFdDi6g3ma/cPZ5ZPZM4AWQikDpq/0llnGGlIL+j8afzw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.2.4.tgz", - "integrity": "sha512-tfOuvUQeo7Hz+FcuOd3LfXVp+342pnWUJ7D2y8NUpu1Ww6xnTbHLpz018/y6rtbHifJ3iIEf9ttxXd8KG7nL0Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-2.0.5.tgz", - "integrity": "sha512-lRZSmtl+DSjok3u9hTWpmkxFZnz7stkbZxzKc08aDUsdrWwhSgWo8yq9rq9DaFUtbAyAq2xnH92fj01S+pwIww==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/color-helpers": "^4.2.1", - "@csstools/css-calc": "^1.2.4" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", - "integrity": "sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^2.4.1" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", - "integrity": "sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz", - "integrity": "sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - } - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.6.tgz", - "integrity": "sha512-Xt00qGAQyqAODFiFEJNkTpSUz5VfYqnDLECdlA/Vv17nl/OIV5QfTRHGAXrBGG5YcJyHpJ+GF9gF/RZvOQz4oA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^3.1.1", - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "3.0.19", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.19.tgz", - "integrity": "sha512-d1OHEXyYGe21G3q88LezWWx31ImEDdmINNDy0LyLNN9ChgN2bPxoubUPiHf9KmwypBMaHmNcMuA/WZOKdZk/Lg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.19.tgz", - "integrity": "sha512-mLvQlMX+keRYr16AuvuV8WYKUwF+D0DiCqlBdvhQ0KYEtcQl9/is9Ssg7RcIys8x0jIn2h1zstS4izckdZj9wg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-content-alt-text": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-1.0.0.tgz", - "integrity": "sha512-SkHdj7EMM/57GVvSxSELpUg7zb5eAndBeuvGwFzYtU06/QXJ/h9fuK7wO5suteJzGhm3GDF/EWPCdWV2h1IGHQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-exponential-functions": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.9.tgz", - "integrity": "sha512-x1Avr15mMeuX7Z5RJUl7DmjhUtg+Amn5DZRD0fQ2TlTFTcJS8U1oxXQ9e5mA62S2RJgUU6db20CRoJyDvae2EQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^1.2.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-3.0.2.tgz", - "integrity": "sha512-E0xz2sjm4AMCkXLCFvI/lyl4XO6aN1NCSMMVEOngFDJ+k2rDwfr6NDjWljk1li42jiLNChVX+YFnmfGCigZKXw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gamut-mapping": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.11.tgz", - "integrity": "sha512-KrHGsUPXRYxboXmJ9wiU/RzDM7y/5uIefLWKFSc36Pok7fxiPyvkSHO51kh+RLZS1W5hbqw9qaa6+tKpTSxa5g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "4.0.20", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.20.tgz", - "integrity": "sha512-ZFl2JBHano6R20KB5ZrB8KdPM2pVK0u+/3cGQ2T8VubJq982I2LSOvQ4/VtxkAXjkPkk1rXt4AD1ni7UjTZ1Og==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.18.tgz", - "integrity": "sha512-3ifnLltR5C7zrJ+g18caxkvSRnu9jBBXCYgnBznRjxm6gQJGnnCO9H6toHfywNdNr/qkiVf2dymERPQLDnjLRQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-3.0.7.tgz", - "integrity": "sha512-YoaNHH2wNZD+c+rHV02l4xQuDpfR8MaL7hD45iJyr+USwvr0LOheeytJ6rq8FN6hXBmEeoJBeXXgGmM8fkhH4g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-1.0.1.tgz", - "integrity": "sha512-wtb+IbUIrIf8CrN6MLQuFR7nlU5C7PwuebfeEXfjthUha1+XZj2RVi+5k/lukToA24sZkYAiSJfHM8uG/UZIdg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.8.tgz", - "integrity": "sha512-0aj591yGlq5Qac+plaWCbn5cpjs5Sh0daovYUKJUOMjIp70prGH/XPLp7QjxtbFXz3CTvb0H9a35dpEuIuUi3Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^3.1.1", - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-light-dark-function": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-1.0.8.tgz", - "integrity": "sha512-x0UtpCyVnERsplUeoaY6nEtp1HxTf4lJjoK/ULEm40DraqFfUdUSt76yoOyX5rGY6eeOUOkurHyYlFHVKv/pew==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-2.0.1.tgz", - "integrity": "sha512-SsrWUNaXKr+e/Uo4R/uIsqJYt3DaggIh/jyZdhy/q8fECoJSKsSMr7nObSLdvoULB69Zb6Bs+sefEIoMG/YfOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overflow": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-1.0.1.tgz", - "integrity": "sha512-Kl4lAbMg0iyztEzDhZuQw8Sj9r2uqFDcU1IPl+AAt2nue8K/f1i7ElvKtXkjhIAmKiy5h2EY8Gt/Cqg0pYFDCw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overscroll-behavior": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-1.0.1.tgz", - "integrity": "sha512-+kHamNxAnX8ojPCtV8WPcUP3XcqMFBSDuBuvT6MHgq7oX4IQxLIXKx64t7g9LiuJzE7vd06Q9qUYR6bh4YnGpQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-2.0.1.tgz", - "integrity": "sha512-W5Gtwz7oIuFcKa5SmBjQ2uxr8ZoL7M2bkoIf0T1WeNqljMkBrfw1DDA8/J83k57NQ1kcweJEjkJ04pUkmyee3A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.11.tgz", - "integrity": "sha512-ElITMOGcjQtvouxjd90WmJRIw1J7KMP+M+O87HaVtlgOOlDt1uEPeTeii8qKGe2AiedEp0XOGIo9lidbiU2Ogg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-minmax": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.8.tgz", - "integrity": "sha512-KYQCal2i7XPNtHAUxCECdrC7tuxIWQCW+s8eMYs5r5PaAiVTeKwlrkRS096PFgojdNCmHeG0Cb7njtuNswNf+w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^1.2.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/media-query-list-parser": "^2.1.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.11.tgz", - "integrity": "sha512-YD6jrib20GRGQcnOu49VJjoAnQ/4249liuz7vTpy/JfgqQ1Dlc5eD4HPUMNLOw9CWey9E6Etxwf/xc/ZF8fECA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/media-query-list-parser": "^2.1.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-3.0.2.tgz", - "integrity": "sha512-ySUmPyawiHSmBW/VI44+IObcKH0v88LqFe0d09Sb3w4B1qjkaROc6d5IA3ll9kjD46IIX/dbO5bwFN/swyoyZA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-3.0.2.tgz", - "integrity": "sha512-fCapyyT/dUdyPtrelQSIV+d5HqtTgnNP/BEG9IuhgXHt93Wc4CfC1bQ55GzKAjWrZbgakMQ7MLfCXEf3rlZJOw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "3.0.19", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.19.tgz", - "integrity": "sha512-e3JxXmxjU3jpU7TzZrsNqSX4OHByRC3XjItV3Ieo/JEQmLg5rdOL4lkv/1vp27gXemzfNt44F42k/pn0FpE21Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.3.0.tgz", - "integrity": "sha512-W2oV01phnILaRGYPmGFlL2MT/OgYjQDrL9sFlbdikMFi6oQkFki9B86XqEWR7HCsTZFVq7dbzr/o71B75TKkGg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.19.tgz", - "integrity": "sha512-MxUMSNvio1WwuS6WRLlQuv6nNPXwIWUFzBBAvL/tBdWfiKjiJnAa6eSSN5gtaacSqUkQ/Ce5Z1OzLRfeaWhADA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-3.0.1.tgz", - "integrity": "sha512-3ZFonK2gfgqg29gUJ2w7xVw2wFJ1eNWVDONjbzGkm73gJHVCYK5fnCqlLr+N+KbEfv2XbWAO0AaOJCFB6Fer6A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.10.tgz", - "integrity": "sha512-MZwo0D0TYrQhT5FQzMqfy/nGZ28D1iFtpN7Su1ck5BPHS95+/Y5O9S4kEvo76f2YOsqwYcT8ZGehSI1TnzuX2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^1.2.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.7.tgz", - "integrity": "sha512-+cptcsM5r45jntU6VjotnkC9GteFR7BQBfZ5oW7inLCxj7AfLGAzMbZ60hKTP13AULVZBdxky0P8um0IBfLHVA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/color-helpers": "^4.2.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.10.tgz", - "integrity": "sha512-G9G8moTc2wiad61nY5HfvxLiM/myX0aYK4s1x8MQlPH29WDPxHQM7ghGgvv2qf2xH+rrXhztOmjGHJj4jsEqXw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^1.2.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-3.0.1.tgz", - "integrity": "sha512-dbDnZ2ja2U8mbPP0Hvmt2RMEGBiF1H7oY6HYSpjteXJGihYwgxgTr6KRbbJ/V6c+4wd51M+9980qG4gKVn5ttg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/selector-resolve-nested": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz", - "integrity": "sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^6.0.13" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", - "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^6.0.13" - } - }, - "node_modules/@csstools/utilities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-1.0.0.tgz", - "integrity": "sha512-tAgvZQe/t2mlvpNosA4+CkMiZ2azISW5WPAcdSalZlEjQvUfghHxfQcrCiK/7/CrfAWVxyM88kGFYO82heIGDg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/compat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.3.tgz", - "integrity": "sha512-wlZhwlDFxkxIZ571aH0FoK4h4Vwx7P3HJx62Gp8hTc10bfpwT2x0nULuAHmQSJBOWPgPeVf+9YtnD4j50zVHmA==", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": "^9.10.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/config-array": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", - "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", - "dev": true, - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/core": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", - "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", - "dev": true, - "dependencies": { - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", - "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", - "dependencies": { - "@floating-ui/utils": "^0.2.8" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.12", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", - "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.8" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" - }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", - "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.1.tgz", - "integrity": "sha512-8dBIHbfsKlCk2jHQ9PoRBg2Z+4TwyE3vZICSnoDlnsHA6SiMlTwfmW6yX0lHsRmWJugkeb92sA0hZdkXJhuz+g==", - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.1.tgz", - "integrity": "sha512-BTKc0b0mgjWZ2UDKVgmwaE0qt0cZs6ITcDgjrti5f/ki7aF5zs+N91V6hitGo3TItCFtnKg6cUVGdTmBFICFRg==", - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", - "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.3" - } - }, - "node_modules/@headlessui/react": { - "version": "1.7.19", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz", - "integrity": "sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==", - "dependencies": { - "@tanstack/react-virtual": "^3.0.0-beta.60", - "client-only": "^0.0.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^16 || ^17 || ^18", - "react-dom": "^16 || ^17 || ^18" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@kurkle/color": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", - "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==" - }, - "node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" - }, - "node_modules/@lezer/cpp": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.2.tgz", - "integrity": "sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/css": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.9.tgz", - "integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/go": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.0.tgz", - "integrity": "sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/highlight": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", - "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/html": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", - "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/java": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz", - "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/javascript": { - "version": "1.4.19", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.19.tgz", - "integrity": "sha512-j44kbR1QL26l6dMunZ1uhKBFteVGLVCBGNUD2sUaMnic+rbTviVuoK0CD1l9FTW31EueWvFFswCKMH7Z+M3JRA==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.1.3", - "@lezer/lr": "^1.3.0" - } - }, - "node_modules/@lezer/json": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", - "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/lr": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", - "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/markdown": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.3.2.tgz", - "integrity": "sha512-Wu7B6VnrKTbBEohqa63h5vxXjiC4pO5ZQJ/TDbhJxPQaaIoRD/6UVDhSDtVsCwVZV12vvN9KxuLL3ATMnlG0oQ==", - "dependencies": { - "@lezer/common": "^1.0.0", - "@lezer/highlight": "^1.0.0" - } - }, - "node_modules/@lezer/php": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.2.tgz", - "integrity": "sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.1.0" - } - }, - "node_modules/@lezer/python": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.14.tgz", - "integrity": "sha512-ykDOb2Ti24n76PJsSa4ZoDF0zH12BSw1LGfQXCYJhJyOGiFTfGaX0Du66Ze72R+u/P35U+O6I9m8TFXov1JzsA==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/rust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz", - "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/sass": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.0.7.tgz", - "integrity": "sha512-8HLlOkuX/SMHOggI2DAsXUw38TuURe+3eQ5hiuk9QmYOUyC55B1dYEIMkav5A4IELVaW4e1T4P9WRiI5ka4mdw==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/xml": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.5.tgz", - "integrity": "sha512-VFouqOzmUWfIg+tfmpcdV33ewtK+NSwd4ngSe1aG7HFb4BN0ExyY1b8msp+ndFrnlG4V4iC8yXacjFtrwERnaw==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/yaml": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz", - "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.4.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@preact/signals-core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.8.0.tgz", - "integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/@preact/signals-react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@preact/signals-react/-/signals-react-2.2.0.tgz", - "integrity": "sha512-EPYlhXqqcOUxz2gTQGt4rtK6X7Jr04517DcJVZ4I5a7Gxy39haK24uFeVWtiU/tnEReRFcxpQN6poYra1jf68A==", - "dependencies": { - "@preact/signals-core": "^1.7.0", - "use-sync-external-store": "^1.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - }, - "peerDependencies": { - "react": "^16.14.0 || 17.x || 18.x" - } - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", - "integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", - "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context-menu": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.2.tgz", - "integrity": "sha512-99EatSTpW+hRYHt7m8wdDlLtkmTovEe8Z/hnxUPV+SKuuNL5HWNhQI4QSdjZqNSgXHay2z4M3Dym73j9p2Gx5Q==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-menu": "2.1.2", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", - "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.6.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", - "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", - "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz", - "integrity": "sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-menu": "2.1.2", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", - "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-icons": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", - "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", - "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", - "integrity": "sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-roving-focus": "1.1.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.6.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", - "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", - "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", - "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz", - "integrity": "sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", - "integrity": "sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-roving-focus": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", - "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" - }, - "node_modules/@remix-run/router": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", - "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", - "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz", - "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz", - "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz", - "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz", - "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz", - "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz", - "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz", - "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz", - "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz", - "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz", - "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz", - "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz", - "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz", - "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz", - "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz", - "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz", - "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz", - "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sentry-internal/feedback": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.120.0.tgz", - "integrity": "sha512-+nU2PXMAyrYyK64PlfxXyRZ+LIl6IWAcdnBeX916WqOJy2WWmtdOrAX8muVwLVIXHzp1EMG1nEZgtpL/Vr2XKQ==", - "dependencies": { - "@sentry/core": "7.120.0", - "@sentry/types": "7.120.0", - "@sentry/utils": "7.120.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry-internal/replay-canvas": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.120.0.tgz", - "integrity": "sha512-ZEFZBP+Jxmy/8IY7IZDZVPqAJ6pPxAFo1lNTd8xfpbno3WAtHw0FLewLfjrFt0zfIgCk8EXj4PW355zRP3C2NQ==", - "dependencies": { - "@sentry/core": "7.120.0", - "@sentry/replay": "7.120.0", - "@sentry/types": "7.120.0", - "@sentry/utils": "7.120.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry-internal/tracing": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.0.tgz", - "integrity": "sha512-VymJoIGMV0PcTJyshka9uJ1sKpR7bHooqW5jTEr6g0dYAwB723fPXHjVW+7SETF7i5+yr2KMprYKreqRidKyKA==", - "dependencies": { - "@sentry/core": "7.120.0", - "@sentry/types": "7.120.0", - "@sentry/utils": "7.120.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/babel-plugin-component-annotate": { - "version": "2.22.6", - "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.6.tgz", - "integrity": "sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@sentry/browser": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.120.0.tgz", - "integrity": "sha512-2hRE3QPLBBX+qqZEHY2IbJv4YvfXY7m/bWmNjN15phyNK3oBcm2Pa8ZiKUYrk8u/4DCEGzNUlhOmFgaxwSfpNw==", - "dependencies": { - "@sentry-internal/feedback": "7.120.0", - "@sentry-internal/replay-canvas": "7.120.0", - "@sentry-internal/tracing": "7.120.0", - "@sentry/core": "7.120.0", - "@sentry/integrations": "7.120.0", - "@sentry/replay": "7.120.0", - "@sentry/types": "7.120.0", - "@sentry/utils": "7.120.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/bundler-plugin-core": { - "version": "2.22.6", - "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.6.tgz", - "integrity": "sha512-1esQdgSUCww9XAntO4pr7uAM5cfGhLsgTK9MEwAKNfvpMYJi9NUTYa3A7AZmdA8V6107Lo4OD7peIPrDRbaDCg==", - "dependencies": { - "@babel/core": "^7.18.5", - "@sentry/babel-plugin-component-annotate": "2.22.6", - "@sentry/cli": "^2.36.1", - "dotenv": "^16.3.1", - "find-up": "^5.0.0", - "glob": "^9.3.2", - "magic-string": "0.30.8", - "unplugin": "1.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@sentry/cli": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.39.0.tgz", - "integrity": "sha512-1Zb2F/yuccNqzbgj/I12Rktm0vtOobkG6I75/6o9Tk+nNuCD7tC+SO5b8bUDKrc3gDE+AI067wbaPKnXXjAwig==", - "hasInstallScript": true, - "dependencies": { - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.7", - "progress": "^2.0.3", - "proxy-from-env": "^1.1.0", - "which": "^2.0.2" - }, - "bin": { - "sentry-cli": "bin/sentry-cli" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@sentry/cli-darwin": "2.39.0", - "@sentry/cli-linux-arm": "2.39.0", - "@sentry/cli-linux-arm64": "2.39.0", - "@sentry/cli-linux-i686": "2.39.0", - "@sentry/cli-linux-x64": "2.39.0", - "@sentry/cli-win32-i686": "2.39.0", - "@sentry/cli-win32-x64": "2.39.0" - } - }, - "node_modules/@sentry/cli-darwin": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.39.0.tgz", - "integrity": "sha512-D3MbVK1gv3NIdwocYESbSWsiDRphmnXILFKKvfqTqnJ07oMh8qHh+saCBCUj8sECSinZIvWJP2weERg4zy8WsA==", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-arm": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.0.tgz", - "integrity": "sha512-vL0X4hbujasgse7+ip06eOmgAxwQe82MFPZHtJhGz4okVwKkfaXat+2Quzl5qPpTBMNLbRCTQVs+yROF7MaNew==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-arm64": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.0.tgz", - "integrity": "sha512-TAm5xQlrXr8aznQqvBvhTfS8+hbBdpoQ7SMVWtwI00Pf0dcg0N/yTGKAh9d25broPnZNyA2UjOrL4LuwLMc9Xw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-i686": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.0.tgz", - "integrity": "sha512-Vytl5egCAuzOQeex8m/iKBGYKM+7uVBCZnEGlx+QJy7OgK9N/UEepeGZ8ISuoZJC4lQKHTi2eve5ZweXwdBLyw==", - "cpu": [ - "x86", - "ia32" - ], - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-linux-x64": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.0.tgz", - "integrity": "sha512-eeuQKiqffU8lzsG3PLCBtHPnla25pnir5H2v1EoBc/Q7N/zuwUWhKzrecxbS4H9hPQQD88NfW9JxHSLCGclxQA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-win32-i686": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.0.tgz", - "integrity": "sha512-kkQo5rrEToR8H1zKpnOyAaMi4xFVOg5RhklwiB1YDlv6+cpy4lWISNWkUhVpe6jMIHaxvADLzYOPwbyBVsWiqg==", - "cpu": [ - "x86", - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-win32-x64": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.0.tgz", - "integrity": "sha512-QVuw/WIWKRASGOKmVUiXIDGIFSqMFUZFWR2wSPmbTqxgTTsoDNYBQIYLsOw/bT+tkZBMiUxVdsdDKofPlHwzdw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/core": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.120.0.tgz", - "integrity": "sha512-uTc2sUQ0heZrMI31oFOHGxjKgw16MbV3C2mcT7qcrb6UmSGR9WqPOXZhnVVuzPWCnQ8B5IPPVdynK//J+9/m6g==", - "dependencies": { - "@sentry/types": "7.120.0", - "@sentry/utils": "7.120.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/integrations": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.120.0.tgz", - "integrity": "sha512-/Hs9MgSmG4JFNyeQkJ+MWh/fxO/U38Pz0VSH3hDrfyCjI8vH9Vz9inGEQXgB9Ke4eH8XnhsQ7xPnM27lWJts6g==", - "dependencies": { - "@sentry/core": "7.120.0", - "@sentry/types": "7.120.0", - "@sentry/utils": "7.120.0", - "localforage": "^1.8.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/react": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.120.0.tgz", - "integrity": "sha512-YTzmTRO9a2ZIdZiiT3Ob4h8/wLDEDC24qrUqomrYHG8Rcj+9EHjTqQQmoB8ARw9Kh0SrIzR5jbDK7C8JO6jzCQ==", - "dependencies": { - "@sentry/browser": "7.120.0", - "@sentry/core": "7.120.0", - "@sentry/types": "7.120.0", - "@sentry/utils": "7.120.0", - "hoist-non-react-statics": "^3.3.2" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "react": "15.x || 16.x || 17.x || 18.x" - } - }, - "node_modules/@sentry/replay": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.120.0.tgz", - "integrity": "sha512-wV9fIYwNtMvFOHQB5eSm+kCorRXsX5+v1DxyTC8Lee1hfzcUQ2Wvqh75VktpXuM9TeZE8h7aQ4Wo4qCgTUdtvA==", - "dependencies": { - "@sentry-internal/tracing": "7.120.0", - "@sentry/core": "7.120.0", - "@sentry/types": "7.120.0", - "@sentry/utils": "7.120.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry/types": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.120.0.tgz", - "integrity": "sha512-3mvELhBQBo6EljcRrJzfpGJYHKIZuBXmqh0y8prh03SWE62pwRL614GIYtd4YOC6OP1gfPn8S8h9w3dD5bF5HA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/utils": { - "version": "7.120.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.120.0.tgz", - "integrity": "sha512-XZsPcBHoYu4+HYn14IOnhabUZgCF99Xn4IdWn8Hjs/c+VPtuAVDhRTsfPyPrpY3OcN8DgO5fZX4qcv/6kNbX1A==", - "dependencies": { - "@sentry/types": "7.120.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/vite-plugin": { - "version": "2.22.6", - "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-2.22.6.tgz", - "integrity": "sha512-zIieP1VLWQb3wUjFJlwOAoaaJygJhXeUoGd0e/Ha2RLb2eW2S+4gjf6y6NqyY71tZ74LYVZKg/4prB6FAZSMXQ==", - "dependencies": { - "@sentry/bundler-plugin-core": "2.22.6", - "unplugin": "1.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@swc/core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.9.3.tgz", - "integrity": "sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.17" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.9.3", - "@swc/core-darwin-x64": "1.9.3", - "@swc/core-linux-arm-gnueabihf": "1.9.3", - "@swc/core-linux-arm64-gnu": "1.9.3", - "@swc/core-linux-arm64-musl": "1.9.3", - "@swc/core-linux-x64-gnu": "1.9.3", - "@swc/core-linux-x64-musl": "1.9.3", - "@swc/core-win32-arm64-msvc": "1.9.3", - "@swc/core-win32-ia32-msvc": "1.9.3", - "@swc/core-win32-x64-msvc": "1.9.3" - }, - "peerDependencies": { - "@swc/helpers": "*" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.3.tgz", - "integrity": "sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.9.3.tgz", - "integrity": "sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.9.3.tgz", - "integrity": "sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.9.3.tgz", - "integrity": "sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.9.3.tgz", - "integrity": "sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.3.tgz", - "integrity": "sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.3.tgz", - "integrity": "sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.9.3.tgz", - "integrity": "sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.9.3.tgz", - "integrity": "sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.9.3.tgz", - "integrity": "sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true - }, - "node_modules/@swc/plugin-styled-components": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@swc/plugin-styled-components/-/plugin-styled-components-2.0.12.tgz", - "integrity": "sha512-ZlA2T++rRRH734gt3+/FGfQc/oIWVQ69xl+FbeWQflB8YYG30/ADCqDOJ90XXo21hk3FZ0sMilXfaqqFUvYy0g==", - "dev": true, - "dependencies": { - "@swc/counter": "^0.1.3" - } - }, - "node_modules/@swc/types": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", - "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", - "dev": true, - "dependencies": { - "@swc/counter": "^0.1.3" - } - }, - "node_modules/@tanstack/react-virtual": { - "version": "3.10.9", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.9.tgz", - "integrity": "sha512-OXO2uBjFqA4Ibr2O3y0YMnkrRWGVNqcvHQXmGvMu6IK8chZl3PrDxFXdGZ2iZkSrKh3/qUYoFqYe+Rx23RoU0g==", - "dependencies": { - "@tanstack/virtual-core": "3.10.9" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.10.9", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.9.tgz", - "integrity": "sha512-kBknKOKzmeR7lN+vSadaKWXaLS0SZZG+oqpQ/k80Q6g9REn6zRHS/ZYdrIzHnpHgy/eWs00SujveUN/GJT2qTw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz", - "integrity": "sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==", - "dev": true, - "dependencies": { - "@babel/generator": "7.17.7", - "@babel/parser": "^7.20.5", - "@babel/traverse": "7.23.2", - "@babel/types": "7.17.0", - "javascript-natural-sort": "0.7.1", - "lodash": "^4.17.21" - }, - "peerDependencies": { - "@vue/compiler-sfc": "3.x", - "prettier": "2.x - 3.x" - }, - "peerDependenciesMeta": { - "@vue/compiler-sfc": { - "optional": true - } - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/generator": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", - "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@types/codemirror": { - "version": "5.60.15", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.15.tgz", - "integrity": "sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==", - "dev": true, - "dependencies": { - "@types/tern": "*" - } - }, - "node_modules/@types/debounce": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz", - "integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" - }, - "node_modules/@types/events": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", - "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==", - "dev": true - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", - "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.17.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.7.tgz", - "integrity": "sha512-sZXXnpBFMKbao30dUAvzKbdwA2JM1fwUtVEq/kxKuPI5mMwZiRElCpTXb0Biq/LMEVpXDZL5G5V0RPnxKeyaYg==", - "dev": true, - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" - }, - "node_modules/@types/qrcode.react": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.5.tgz", - "integrity": "sha512-BghPtnlwvrvq8QkGa1H25YnN+5OIgCKFuQruncGWLGJYOzeSKiix/4+B9BtfKF2wf5ja8yfyWYA3OXju995G8w==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-copy-to-clipboard": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.7.tgz", - "integrity": "sha512-Gft19D+as4M+9Whq1oglhmK49vqPhcLzk8WfvfLvaYMIPYanyfLy0+CwFucMJfdKoSFyySPmkkWn8/E6voQXjQ==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-redux": { - "version": "7.1.34", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz", - "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==", - "dev": true, - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "node_modules/@types/stylis": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", - "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" - }, - "node_modules/@types/tern": { - "version": "0.23.9", - "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", - "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", - "dev": true, - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitejs/plugin-react-swc": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.1.tgz", - "integrity": "sha512-vgWOY0i1EROUK0Ctg1hwhtC3SdcDjZcdit4Ups4aPkDcB1jYhmo+RMYWY87cmXMhvtD5uf8lV89j2w16vkdSVg==", - "dev": true, - "dependencies": { - "@swc/core": "^1.7.26" - }, - "peerDependencies": { - "vite": "^4 || ^5" - } - }, - "node_modules/@xterm/addon-fit": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", - "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } - }, - "node_modules/@xterm/addon-search": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.15.0.tgz", - "integrity": "sha512-ZBZKLQ+EuKE83CqCmSSz5y1tx+aNOCUaA7dm6emgOX+8J9H1FWXZyrKfzjwzV+V14TV3xToz1goIeRhXBS5qjg==", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } - }, - "node_modules/@xterm/addon-web-links": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.11.0.tgz", - "integrity": "sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } - }, - "node_modules/@xterm/xterm": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", - "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==" - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/aria-hidden": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", - "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/autoprefixer": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", - "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "browserslist": "^4.23.3", - "caniuse-lite": "^1.0.30001646", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001684", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", - "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chart.js": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz", - "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cmdk": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.4.tgz", - "integrity": "sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==", - "dependencies": { - "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-id": "^1.1.0", - "@radix-ui/react-primitive": "^2.0.0", - "use-sync-external-store": "^1.2.2" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" - }, - "node_modules/copy-to-clipboard": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", - "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", - "dependencies": { - "toggle-selection": "^1.0.6" - } - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" - }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-blank-pseudo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.2.tgz", - "integrity": "sha512-J/6m+lsqpKPqWHOifAFtKFeGLOzw3jR92rxQcwRUfA/eTuZzKfKlxOmYDx2+tqOPQAueNvBiY8WhAeHu5qNmTg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-has-pseudo": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.5.tgz", - "integrity": "sha512-ZTv6RlvJJZKp32jPYnAJVhowDCrRrHUTAxsYSuUPBEDJjzws6neMnzkRblxtgmv1RgcV5dhH2gn7E3wA9Wt6lw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^3.1.1", - "postcss-selector-parser": "^6.0.13", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-9.0.1.tgz", - "integrity": "sha512-iFit06ochwCKPRiWagbTa1OAWCvWWVdEnIFd8BaRrgO8YrrNh4RAWUQTFcYX5tdFZgFl1DJ3iiULchZyEbnF4g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/cssdb": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.1.tgz", - "integrity": "sha512-KwEPys7lNsC8OjASI8RrmwOYYDcm0JOW9zQhcV83ejYcQkirTEyeAGui8aO2F5PiS6SLpxuTzl6qlMElIdsgIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } - ] - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/debounce": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", - "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deepmerge-ts": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", - "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "node_modules/easy-peasy": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/easy-peasy/-/easy-peasy-6.0.5.tgz", - "integrity": "sha512-JRtbPp0uYVjCjG6CFr+lg7WqSHhB8p8SGBJihC2h3veP2a0EwUDYMlsSfWrYL2H7EiETVAlMbSusa5h2Tej6iQ==", - "dependencies": { - "@babel/runtime": "^7.22.6", - "fast-deep-equal": "^3.1.3", - "immer": "^9.0.21", - "redux": "^4.1.2", - "redux-thunk": "^2.4.1", - "ts-toolbelt": "^9.6.0", - "use-sync-external-store": "^1.2.0" - }, - "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.64", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz", - "integrity": "sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.23.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", - "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-iterator-helpers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", - "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.3", - "safe-array-concat": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", - "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.15.0", - "@eslint/plugin-kit": "^0.2.3", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.5", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-node/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": "*", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.37.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", - "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.2", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.1.0", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.8", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.0", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formik": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", - "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==", - "funding": [ - { - "type": "individual", - "url": "https://opencollective.com/formik" - } - ], - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.1", - "deepmerge": "^2.1.1", - "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "react-fast-compare": "^2.0.1", - "tiny-warning": "^1.0.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/formik/node_modules/react-fast-compare": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", - "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/framer-motion": { - "version": "11.11.17", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.11.17.tgz", - "integrity": "sha512-O8QzvoKiuzI5HSAHbcYuL6xU+ZLXbrH7C8Akaato4JzQbX2ULNeniqC2Vo5eiCtFktX9XsJ+7nUhxcl2E2IjpA==", - "dependencies": { - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", - "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "void-elements": "3.1.0" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/i18next": { - "version": "25.0.2", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.0.2.tgz", - "integrity": "sha512-xWxgK8GAaPYkV9ia2tdgbtdM+qiC+ysVTBPvXhpCORU/+QkeQe3BSI7Crr+c4ZXULN1PfnXG/HY2n7HGx4KKBg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.10" - }, - "peerDependencies": { - "typescript": "^5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/i18next-browser-languagedetector": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.5.tgz", - "integrity": "sha512-OstebRKqKiQw8xEvQF5aRyUujsCatanj7Q9eo5iiH2gJpoXGZ7483ol3sVBwfqbobTQPNH1J+NAyJ1aCQoEC+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.2" - } - }, - "node_modules/i18next-http-backend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", - "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", - "license": "MIT", - "dependencies": { - "cross-fetch": "4.0.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", - "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/iterator.prototype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", - "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", - "dev": true - }, - "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/laravel-vite-plugin": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.6.tgz", - "integrity": "sha512-B34OqmZc/rV1KvSjst8SsUm/LKHsuDusw8jiZCIhlnTHXbXnK89JUM9pTJuk6E/Vc/1DT2gX7qNfhipak1WS8w==", - "dependencies": { - "picocolors": "^1.0.0", - "vite-plugin-full-reload": "^1.1.0" - }, - "bin": { - "clean-orphaned-assets": "bin/clean.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "dependencies": { - "lie": "3.1.1" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/million": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/million/-/million-3.1.11.tgz", - "integrity": "sha512-6Vh1s0da0PzSqbbp9Zd8yMTIkOWnvBU4vNJCMHTZPXaY3fZ5h+N7s5croS/RBgjJIHz3WQZnvyNBQz7gQ6cqJg==", - "dependencies": { - "@babel/core": "^7.23.7", - "@babel/types": "^7.23.6", - "@rollup/pluginutils": "^5.1.0", - "kleur": "^4.1.5", - "undici": "^6.3.0", - "unplugin": "^1.6.0" - }, - "bin": { - "million": "cli.js" - }, - "funding": { - "url": "https://github.com/sponsors/aidenybai" - } - }, - "node_modules/million/node_modules/unplugin": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.0.tgz", - "integrity": "sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==", - "dependencies": { - "acorn": "^8.14.0", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/million/node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.3.tgz", - "integrity": "sha512-KHkmCILThWBRtg+Jn1owTnHPnFit4OkqS+eKiGEOPIGke54DCeYGJ6r0Fx/HjfE9M9kznApCLcU0DvnPchazMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.14.tgz", - "integrity": "sha512-dNUX+UH4dAozZ8uMHZ3CtCNYw8fyFAmqqdcyxMr7PEdM9jLXV19YscoYO0F25KqZYhmtWKQ+4tKrIZQrwzwg7A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.4.tgz", - "integrity": "sha512-XQZm4q4fNFqVCYMGPiBjcqDhuG7Ey2xrl99AnDJMyr5eDASsAGalndVgHZF8i97VFNy1GQeZc4q2ydagGmhelQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.3.tgz", - "integrity": "sha512-ruBqzEFDYHrcVq3FnW3XHgwRqVMrtEPLBtD7K2YmsLKVc2jbkxzzNEctJKsPCpDZ+LeMHLKRDoSShVefGc+CkQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-media": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.8.tgz", - "integrity": "sha512-V1KgPcmvlGdxTel4/CyQtBJEFhMVpEmRGFrnVtgfGIHj5PJX9vO36eFBxKBeJn+aCDTed70cc+98Mz3J/uVdGQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.13", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/media-query-list-parser": "^2.1.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-properties": { - "version": "13.3.12", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.12.tgz", - "integrity": "sha512-oPn/OVqONB2ZLNqN185LDyaVByELAA/u3l2CS2TS16x2j2XsmV4kd8U49+TMxmUsEU9d8fB/I10E6U7kB0L1BA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.13", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.12.tgz", - "integrity": "sha512-ctIoprBMJwByYMGjXG0F7IT2iMF2hnamQ+aWZETyBM0aAlyaYdVZTeUkk8RB+9h9wP+NdN3f01lfvKl2ZSqC0g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.13", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "postcss-selector-parser": "^6.1.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-8.0.1.tgz", - "integrity": "sha512-uULohfWBBVoFiZXgsQA24JV6FdKIidQ+ZqxOouhWwdE+qJlALbkS5ScB43ZTjPK+xUZZhlaO/NjfCt5h4IKUfw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.7.tgz", - "integrity": "sha512-1xEhjV9u1s4l3iP5lRt1zvMjI/ya8492o9l/ivcxHhkO3nOz16moC4JpMxDUGrOs4R3hX+KWT7gKoV842cwRgg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-9.0.1.tgz", - "integrity": "sha512-N2VQ5uPz3Z9ZcqI5tmeholn4d+1H14fKXszpjogZIrFbhaq0zNAtq8sAnw6VLiqGbL8YBzsnu7K9bBkTqaRimQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-8.0.1.tgz", - "integrity": "sha512-NFU3xcY/xwNaapVb+1uJ4n23XImoC86JNwkY/uduytSl2s9Ekc2EpzmRR63+ExitnW3Mab3Fba/wRPCT5oDILA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "dev": true, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-5.0.1.tgz", - "integrity": "sha512-k2z9Cnngc24c0KF4MtMuDdToROYqGMMUQGcE6V0odwjHyOHtaDBlLeRBV70y9/vF7KIbShrTRZ70JjsI1BZyWw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-image-set-function": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-6.0.3.tgz", - "integrity": "sha512-i2bXrBYzfbRzFnm+pVuxVePSTCRiNmlfssGI4H0tJQvDue+yywXwUxe68VyzXs7cGtMaH6MCLY6IbCShrSroCw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-import": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz", - "integrity": "sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-lab-function": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.19.tgz", - "integrity": "sha512-vwln/mgvFrotJuGV8GFhpAOu9iGf3pvTBr6dLPDmUcqVD5OsQpEFyQMAFTxSxWXGEzBj6ld4pZ/9GDfEpXvo0g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/utilities": "^1.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/postcss-logical": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-7.0.1.tgz", - "integrity": "sha512-8GwUQZE0ri0K0HJHkDv87XOLC8DE0msc+HoWLeKdtjDZEwpZ5xuK3QdV6FhmHSQW40LPkg43QzvATRAI3LsRkg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nesting": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.5.tgz", - "integrity": "sha512-N1NgI1PDCiAGWPTYrwqm8wpjv0bgDmkYHH72pNsqTCv9CObxjxftdYu6AKtGN+pnJa7FQjMm3v4sp8QJbFsYdQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-resolve-nested": "^1.1.0", - "@csstools/selector-specificity": "^3.1.1", - "postcss-selector-parser": "^6.1.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-2.0.0.tgz", - "integrity": "sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ==", - "dev": true, - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-5.0.1.tgz", - "integrity": "sha512-XzjBYKLd1t6vHsaokMV9URBt2EwC9a7nDhpQpjoPk2HRTSQfokPfyAS/Q7AOrzUu6q+vp/GnrDBGuj/FCaRqrQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "dev": true, - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-9.0.1.tgz", - "integrity": "sha512-JfL+paQOgRQRMoYFc2f73pGuG/Aw3tt4vYMR6UA3cWVMxivviPTnMFnFTczUJOA4K2Zga6xgQVE+PcLs64WC8Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-preset-env": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.6.0.tgz", - "integrity": "sha512-Lxfk4RYjUdwPCYkc321QMdgtdCP34AeI94z+/8kVmqnTIlD4bMRQeGcMZgwz8BxHrzQiFXYIR5d7k/9JMs2MEA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-cascade-layers": "^4.0.6", - "@csstools/postcss-color-function": "^3.0.19", - "@csstools/postcss-color-mix-function": "^2.0.19", - "@csstools/postcss-content-alt-text": "^1.0.0", - "@csstools/postcss-exponential-functions": "^1.0.9", - "@csstools/postcss-font-format-keywords": "^3.0.2", - "@csstools/postcss-gamut-mapping": "^1.0.11", - "@csstools/postcss-gradients-interpolation-method": "^4.0.20", - "@csstools/postcss-hwb-function": "^3.0.18", - "@csstools/postcss-ic-unit": "^3.0.7", - "@csstools/postcss-initial": "^1.0.1", - "@csstools/postcss-is-pseudo-class": "^4.0.8", - "@csstools/postcss-light-dark-function": "^1.0.8", - "@csstools/postcss-logical-float-and-clear": "^2.0.1", - "@csstools/postcss-logical-overflow": "^1.0.1", - "@csstools/postcss-logical-overscroll-behavior": "^1.0.1", - "@csstools/postcss-logical-resize": "^2.0.1", - "@csstools/postcss-logical-viewport-units": "^2.0.11", - "@csstools/postcss-media-minmax": "^1.1.8", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.11", - "@csstools/postcss-nested-calc": "^3.0.2", - "@csstools/postcss-normalize-display-values": "^3.0.2", - "@csstools/postcss-oklab-function": "^3.0.19", - "@csstools/postcss-progressive-custom-properties": "^3.3.0", - "@csstools/postcss-relative-color-syntax": "^2.0.19", - "@csstools/postcss-scope-pseudo-class": "^3.0.1", - "@csstools/postcss-stepped-value-functions": "^3.0.10", - "@csstools/postcss-text-decoration-shorthand": "^3.0.7", - "@csstools/postcss-trigonometric-functions": "^3.0.10", - "@csstools/postcss-unset-value": "^3.0.1", - "autoprefixer": "^10.4.19", - "browserslist": "^4.23.1", - "css-blank-pseudo": "^6.0.2", - "css-has-pseudo": "^6.0.5", - "css-prefers-color-scheme": "^9.0.1", - "cssdb": "^8.1.0", - "postcss-attribute-case-insensitive": "^6.0.3", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^6.0.14", - "postcss-color-hex-alpha": "^9.0.4", - "postcss-color-rebeccapurple": "^9.0.3", - "postcss-custom-media": "^10.0.8", - "postcss-custom-properties": "^13.3.12", - "postcss-custom-selectors": "^7.1.12", - "postcss-dir-pseudo-class": "^8.0.1", - "postcss-double-position-gradients": "^5.0.7", - "postcss-focus-visible": "^9.0.1", - "postcss-focus-within": "^8.0.1", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^5.0.1", - "postcss-image-set-function": "^6.0.3", - "postcss-lab-function": "^6.0.19", - "postcss-logical": "^7.0.1", - "postcss-nesting": "^12.1.5", - "postcss-opacity-percentage": "^2.0.0", - "postcss-overflow-shorthand": "^5.0.1", - "postcss-page-break": "^3.0.4", - "postcss-place": "^9.0.1", - "postcss-pseudo-class-any-link": "^9.0.2", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^7.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.2.tgz", - "integrity": "sha512-HFSsxIqQ9nA27ahyfH37cRWGk3SYyQLpk0LiWw/UGMV4VKT5YG2ONee4Pz/oFesnK0dn2AjcyequDbIjKJgB0g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "dev": true, - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-7.0.2.tgz", - "integrity": "sha512-/SSxf/90Obye49VZIfc0ls4H0P6i6V1iHv0pzZH8SdgvZOPFkF37ef1r5cyWcMflJSFJ5bfuoluTnFnBBFiuSA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.14.tgz", - "integrity": "sha512-Puaz+wPUAhFp8Lo9HuciYKM2Y2XExESjeT+9NQoVFXZsPPnc9VYss2SpxdQ6vbatmt8/4+SN0oe0I1cPDABg9Q==", - "dev": true, - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@prettier/plugin-pug": "*", - "@shopify/prettier-plugin-liquid": "*", - "@trivago/prettier-plugin-sort-imports": "*", - "@zackad/prettier-plugin-twig-melody": "*", - "prettier": "^3.0", - "prettier-plugin-astro": "*", - "prettier-plugin-css-order": "*", - "prettier-plugin-import-sort": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-sort-imports": "*", - "prettier-plugin-style-order": "*", - "prettier-plugin-svelte": "*" - }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "@zackad/prettier-plugin-twig-melody": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-import-sort": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-style-order": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - } - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/property-expr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", - "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qrcode.react": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.2.0.tgz", - "integrity": "sha512-YietHHltOHA4+l5na1srdaMx4sVSOjV9tamHs+mwiLWAMr6QVACRUw1Neax5CptFILcNoITctJY0Ipyn5enQ8g==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-chartjs-2": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", - "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", - "peerDependencies": { - "chart.js": "^4.1.1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" - }, - "node_modules/react-i18next": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.1.tgz", - "integrity": "sha512-C8RZ7N7H0L+flitiX6ASjq9p5puVJU1Z8VyL3OgM/QOMRf40BMZX+5TkpxzZVcTmOLPX5zlti4InEX5pFyiVeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.0", - "html-parse-stringify": "^3.0.1" - }, - "peerDependencies": { - "i18next": ">= 23.2.3", - "react": ">= 16.8.0", - "typescript": "^5" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-remove-scroll": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", - "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.6", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", - "dependencies": { - "react-style-singleton": "^2.2.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-router": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", - "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", - "dependencies": { - "@remix-run/router": "1.21.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", - "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", - "dependencies": { - "@remix-run/router": "1.21.0", - "react-router": "6.28.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dependencies": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reaptcha": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/reaptcha/-/reaptcha-1.12.1.tgz", - "integrity": "sha512-zoppfGKHmo8x4PBmSrIQYQOGgVp1e8wMhr6KbwAdbQ76rSky1DcDCXLWRtBg7HGXn2hw+o+0hknafMB0rnrzZQ==", - "peerDependencies": { - "react": "^16 || ^17 || ^18" - } - }, - "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "peerDependencies": { - "redux": "^4" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz", - "integrity": "sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "which-builtin-type": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", - "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", - "dependencies": { - "@types/estree": "1.0.6" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.27.4", - "@rollup/rollup-android-arm64": "4.27.4", - "@rollup/rollup-darwin-arm64": "4.27.4", - "@rollup/rollup-darwin-x64": "4.27.4", - "@rollup/rollup-freebsd-arm64": "4.27.4", - "@rollup/rollup-freebsd-x64": "4.27.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", - "@rollup/rollup-linux-arm-musleabihf": "4.27.4", - "@rollup/rollup-linux-arm64-gnu": "4.27.4", - "@rollup/rollup-linux-arm64-musl": "4.27.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", - "@rollup/rollup-linux-riscv64-gnu": "4.27.4", - "@rollup/rollup-linux-s390x-gnu": "4.27.4", - "@rollup/rollup-linux-x64-gnu": "4.27.4", - "@rollup/rollup-linux-x64-musl": "4.27.4", - "@rollup/rollup-win32-arm64-msvc": "4.27.4", - "@rollup/rollup-win32-ia32-msvc": "4.27.4", - "@rollup/rollup-win32-x64-msvc": "4.27.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sockette": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/sockette/-/sockette-2.0.6.tgz", - "integrity": "sha512-W6iG8RGV6Zife3Cj+FhuyHV447E6fqFM2hKmnaQrTvg3OydINV3Msj3WPFbX76blUlUxvQSMMMdrJxce8NqI5Q==" - }, - "node_modules/sonner": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.0.tgz", - "integrity": "sha512-W6dH7m5MujEPyug3lpI2l3TC3Pp1+LTgK0Efg+IHDrBbtEjyCmCHHo6yfNBOsf1tFZ6zf+jceWwB38baC8yO9g==", - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", - "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "regexp.prototype.flags": "^1.5.2", - "set-function-name": "^2.0.2", - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-mod": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", - "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" - }, - "node_modules/styled-components": { - "version": "6.1.17", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.17.tgz", - "integrity": "sha512-97D7DwWanI7nN24v0D4SvbfjLE9656umNSJZkBkDIWL37aZqG/wRQ+Y9pWtXyBIM/NSfcBzHLErEsqHmJNSVUg==", - "license": "MIT", - "dependencies": { - "@emotion/is-prop-valid": "1.2.2", - "@emotion/unitless": "0.8.1", - "@types/stylis": "4.2.5", - "css-to-react-native": "3.2.0", - "csstype": "3.1.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.2", - "tslib": "2.6.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" - } - }, - "node_modules/styled-components/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" - }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/swr": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", - "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", - "dependencies": { - "client-only": "^0.0.1", - "use-sync-external-store": "^1.2.0" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", - "dev": true, - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/tailwind-merge": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", - "integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwind-scrollbar": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-3.1.0.tgz", - "integrity": "sha512-pmrtDIZeHyu2idTejfV59SbaJyvp1VRjYxAjZBH0jnyrPRo6HL1kD5Glz8VPagasqr6oAx6M05+Tuw429Z8jxg==", - "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "tailwindcss": "3.x" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", - "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" - } - }, - "node_modules/tailwindcss-inner-border": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/tailwindcss-inner-border/-/tailwindcss-inner-border-0.2.0.tgz", - "integrity": "sha512-IrKWoSHMisGY1FGfwD3+5nzPu1N9gWqUopqma72rS/5wp+DGxUltXhMm84TLpUeaUJyW6NDSHUO/qWh/+puZvg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kripod" - }, - "peerDependencies": { - "tailwindcss": ">=3" - } - }, - "node_modules/tailwindcss/node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tiny-case": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" - }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/ts-api-utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.1.tgz", - "integrity": "sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-essentials": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.2.tgz", - "integrity": "sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==", - "dev": true, - "peerDependencies": { - "typescript": ">=4.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" - }, - "node_modules/ts-toolbelt": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", - "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "node_modules/turbo": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.3.1.tgz", - "integrity": "sha512-vHZe/e6k1HZVKiMQPQ1BWFn53vjVQDFKdkjUq/pBKlRWi1gw9LQO6ntH4qZCcHY1rH6TXgsRmexXdgWl96YvVQ==", - "bin": { - "turbo": "bin/turbo" - }, - "optionalDependencies": { - "turbo-darwin-64": "2.3.1", - "turbo-darwin-arm64": "2.3.1", - "turbo-linux-64": "2.3.1", - "turbo-linux-arm64": "2.3.1", - "turbo-windows-64": "2.3.1", - "turbo-windows-arm64": "2.3.1" - } - }, - "node_modules/turbo-darwin-64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.3.1.tgz", - "integrity": "sha512-tjHfjW/Gs8Q9IO+9gPdIsSStZ8I09QYDRT/SyhFTPLnc7O2ZlxHPBVFfjUkHUjanHNYO8CpRGt+zdp1PaMCruw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/turbo-darwin-arm64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.3.1.tgz", - "integrity": "sha512-At1WStnxCfrBQ4M2g6ynre8WsusGwA11okhVolBxyFUemYozDTtbZwelr+IqNggjT251vviokxOkcFzzogbiFw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/turbo-linux-64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.3.1.tgz", - "integrity": "sha512-COwEev7s9fsxLM2eoRCyRLPj+BXvZjFIS+GxzdAubYhoSoZit8B8QGKczyDl6448xhuFEWKrpHhcR9aBuwB4ag==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/turbo-linux-arm64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.3.1.tgz", - "integrity": "sha512-AP0uE15Rhxza2Jl+Q3gxdXRA92IIeFAYaufz6CMcZuGy9yZsBlLt9w6T47H6g7XQPzWuw8pzfjM1omcTKkkDpQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/turbo-windows-64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.3.1.tgz", - "integrity": "sha512-HDSneq0dNZYZch74c2eygq+OiJE/JYDs7OsGM0yRYVj336383xkUnxz6W2I7qiyMCQXzp4UVUDZXvZhUYcX3BA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/turbo-windows-arm64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.3.1.tgz", - "integrity": "sha512-7/2/sJZiquwoT/jWBCfV0qKq4NarsJPmDRjMcR9dDMIwCYsGM8ljomkDRTCtkNeFcUvYw54MiRWHehWgbcRPsw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/twin.macro": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/twin.macro/-/twin.macro-3.4.1.tgz", - "integrity": "sha512-bxGKTV4u/iGcQqHIugPaW5YSLJ5rIr56ay4Pjcr2Mbb037k341bQ+eWT8z3F7r8ZGTXjTD3uiuxos+qQRy4VjQ==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.22.15", - "babel-plugin-macros": "^3.1.0", - "chalk": "4.1.2", - "lodash.get": "^4.4.2", - "lodash.merge": "^4.6.2", - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": ">=16.14.0" - }, - "peerDependencies": { - "tailwindcss": ">=3.3.1" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", - "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", - "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true - }, - "node_modules/unplugin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.0.1.tgz", - "integrity": "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==", - "dependencies": { - "acorn": "^8.8.1", - "chokidar": "^3.5.3", - "webpack-sources": "^3.2.3", - "webpack-virtual-modules": "^0.5.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", - "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", - "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-plugin-full-reload": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", - "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", - "dependencies": { - "picocolors": "^1.0.0", - "picomatch": "^2.3.1" - } - }, - "node_modules/vite-plugin-manifest-sri": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/vite-plugin-manifest-sri/-/vite-plugin-manifest-sri-0.2.0.tgz", - "integrity": "sha512-Zt5jt19xTIJ91LOuQTCtNG7rTFc5OziAjBz2H5NdCGqaOD1nxrWExLhcKW+W4/q8/jOPCg/n5ncYEQmqCxiGQQ==", - "dev": true - }, - "node_modules/void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", - "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.0.tgz", - "integrity": "sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/yaml": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", - "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yup": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", - "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", - "dependencies": { - "property-expr": "^2.0.5", - "tiny-case": "^1.0.3", - "toposort": "^2.0.2", - "type-fest": "^2.19.0" - } - } - } -} diff --git a/package.json b/package.json index 5dd75d0a5..4b0802f6b 100644 --- a/package.json +++ b/package.json @@ -7,114 +7,112 @@ }, "type": "module", "dependencies": { - "@codemirror/autocomplete": "^6.16.0", - "@codemirror/commands": "^6.3.3", + "@codemirror/autocomplete": "^6.18.6", + "@codemirror/commands": "^6.8.1", + "@codemirror/language": "^6.11.2", "@codemirror/language-data": "^6.5.1", - "@codemirror/legacy-modes": "^6.4.0", - "@codemirror/lint": "^6.8.2", - "@codemirror/search": "^6.5.7", - "@codemirror/state": "^6.4.1", - "@codemirror/view": "^6.34.3", - "@eslint/compat": "^1.2.2", - "@fortawesome/fontawesome-svg-core": "^6.6.0", - "@fortawesome/free-solid-svg-icons": "^6.6.0", - "@fortawesome/react-fontawesome": "^0.2.2", - "@headlessui/react": "^1.7.18", - "@preact/signals-react": "^2.0.1", - "@radix-ui/react-checkbox": "^1.0.4", - "@radix-ui/react-context-menu": "^2.1.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-switch": "^1.0.3", - "@radix-ui/react-tabs": "^1.0.4", - "@sentry/react": "^7.120.0", - "@sentry/vite-plugin": "^2.16.1", - "@tanstack/react-virtual": "^3.2.1", + "@codemirror/lint": "^6.8.5", + "@codemirror/search": "^6.5.11", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.38.1", + "@date-fns/tz": "^1.3.1", + "@eslint/compat": "^1.3.1", + "@eslint/js": "^9.32.0", + "@hcaptcha/react-hcaptcha": "^1.12.0", + "@headlessui/react": "^2.2.7", + "@lezer/highlight": "^1.2.1", + "@marsidev/react-turnstile": "^1.2.0", + "@preact/signals-react": "^3.2.1", + "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-context-menu": "^2.2.15", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-tooltip": "^1.2.7", + "@tailwindcss/postcss": "^4.1.11", + "@tanstack/react-virtual": "^3.13.12", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-search": "^0.15.0", "@xterm/addon-web-links": "^0.11.0", "@xterm/xterm": "^5.5.0", - "axios": "^1.7.7", - "browserslist": "^4.23.0", - "chart.js": "^4.4.4", - "clsx": "^2.1.0", - "cmdk": "^1.0.0", + "axios": "^1.11.0", + "browserslist": "^4.25.1", + "chart.js": "^4.5.0", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", "copy-to-clipboard": "^3.3.3", - "date-fns": "^3.6.0", + "date-fns": "^4.1.0", "debounce": "^2.2.0", - "deepmerge-ts": "^7.1.0", - "easy-peasy": "^6.0.4", + "deepmerge-ts": "^7.1.5", + "easy-peasy": "^6.1.0", + "eslint-plugin-turbo": "^2.5.5", "events": "^3.3.0", - "formik": "^2.4.5", - "framer-motion": "^11.11.11", - "globals": "^15.9.0", - "laravel-vite-plugin": "^1.0.2", - "million": "^3.0.6", - "pathe": "^1.1.2", - "qrcode.react": "^3.1.0", - "react": "^18.3.1", - "react-chartjs-2": "^5.2.0", - "react-dom": "^18.3.0", + "formik": "^2.4.6", + "globals": "^16.3.0", + "install": "^0.13.0", + "laravel-vite-plugin": "^2.0.0", + "million": "^3.1.11", + "motion": "^12.23.12", + "pathe": "^2.0.3", + "qrcode.react": "^4.2.0", + "radix-ui": "^1.4.2", + "react": "^19.1.1", + "react-chartjs-2": "^5.3.0", + "react-dom": "^19.1.1", "react-fast-compare": "^3.2.2", - "react-router-dom": "^6.22.3", - "reaptcha": "^1.12.1", + "react-router-dom": "^7.7.1", "sockette": "^2.0.6", - "sonner": "^1.4.41", - "styled-components": "^6.1.17", - "swr": "^2.2.5", - "tailwind-merge": "^2.2.2", - "tailwind-scrollbar": "^3.1.0", - "tailwindcss": "^3.4.10", - "tailwindcss-animate": "^1.0.7", - "turbo": "^2.0.14", - "uuid": "^9.0.1", - "vite": "^5.4.11", - "yup": "^1.4.0" + "sonner": "^2.0.7", + "styled-components": "^6.1.19", + "swr": "^2.3.4", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.11", + "turbo": "^2.5.5", + "typescript-eslint": "^8.39.0", + "uuid": "^11.1.0", + "vite": "^7.0.6", + "yup": "^1.7.0" }, "devDependencies": { - "@swc/core": "^1.7.10", - "@swc/plugin-styled-components": "^2.0.11", - "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@types/codemirror": "^5.60.15", + "@swc/core": "^1.13.3", + "@swc/plugin-styled-components": "^9.0.2", + "@trivago/prettier-plugin-sort-imports": "^5.2.2", "@types/debounce": "^1.2.4", "@types/events": "^3.0.3", - "@types/node": "^20.12.7", - "@types/qrcode.react": "^1.0.5", - "@types/react": "^18.3.1", + "@types/node": "^24.2.0", + "@types/react": "^19.1.9", "@types/react-copy-to-clipboard": "^5.0.7", - "@types/react-dom": "^18.3.0", - "@types/react-redux": "^7.1.33", - "@types/uuid": "^9.0.8", - "@typescript-eslint/eslint-plugin": "^7.5.0", - "@typescript-eslint/parser": "^7.5.0", - "@vitejs/plugin-react-swc": "^3.6.0", - "autoprefixer": "^10.4.19", - "eslint": "^9.15.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", - "i18next": "^25.0.2", - "i18next-browser-languagedetector": "^8.0.5", - "postcss": "^8.4.38", - "postcss-import": "^16.1.0", - "postcss-nesting": "^12.1.1", - "postcss-preset-env": "^9.5.5", - "prettier": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.5.13", - "react-i18next": "^15.5.1", - "tailwindcss-inner-border": "^0.2.0", - "ts-essentials": "^9.4.2", - "typescript": "^5.4.5", + "@types/react-dom": "^19.1.7", + "@types/uuid": "^10.0.0", + "@vitejs/plugin-react-swc": "^3.11.0", + "autoprefixer": "^10.4.21", + "cross-env": "^10.0.0", + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.3", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "postcss": "^8.5.6", + "postcss-import": "^16.1.1", + "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.6.14", + "tailwind-scrollbar": "^4.0.2", + "ts-essentials": "^10.1.1", + "tw-animate-css": "^1.3.6", + "typescript": "^5.9.2", "vite-plugin-manifest-sri": "^0.2.0" }, "scripts": { - "lint": "eslint ./resources/scripts/**/*.{ts,tsx} --fix", + "lint": "eslint . --fix", "lint:turbo": "turbo lint", "dev": "vite", + "dev:docker": "cross-env APP_URL=\"http://localhost:3000\" vite", + "dev:vagrant": "vagrant up && pnpm run dev", + "compose": "docker compose down && docker compose up -d", + "compose:down": "docker compose down", "build": "vite build", "build:turbo": "turbo build", + "build:docker:dev": "docker buildx build -t pyrodactyl:develop --build-arg DEV=true .", "ship": "turbo lint build" }, "browserslist": [ @@ -123,5 +121,5 @@ "firefox esr", "not dead" ], - "packageManager": "npm@10.8.1" -} + "packageManager": "pnpm@10.13.1" +} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index bf173ed26..f93ee1588 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,31 +1,26 @@ - - - - ./app - - - - - ./tests/Integration - - - ./tests/Unit - - - - - - - - - - - - + + + + ./tests/Integration + + + ./tests/Unit + + + + + + + + + + + + + + + ./app + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 000000000..b7f51d58d --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,7421 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@codemirror/autocomplete': + specifier: ^6.18.6 + version: 6.18.6 + '@codemirror/commands': + specifier: ^6.8.1 + version: 6.8.1 + '@codemirror/language': + specifier: ^6.11.2 + version: 6.11.2 + '@codemirror/language-data': + specifier: ^6.5.1 + version: 6.5.1 + '@codemirror/lint': + specifier: ^6.8.5 + version: 6.8.5 + '@codemirror/search': + specifier: ^6.5.11 + version: 6.5.11 + '@codemirror/state': + specifier: ^6.5.2 + version: 6.5.2 + '@codemirror/view': + specifier: ^6.38.1 + version: 6.38.1 + '@date-fns/tz': + specifier: ^1.3.1 + version: 1.3.1 + '@eslint/compat': + specifier: ^1.3.1 + version: 1.3.1(eslint@9.32.0(jiti@2.4.2)) + '@eslint/js': + specifier: ^9.32.0 + version: 9.32.0 + '@hcaptcha/react-hcaptcha': + specifier: ^1.12.0 + version: 1.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@headlessui/react': + specifier: ^2.2.7 + version: 2.2.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@lezer/highlight': + specifier: ^1.2.1 + version: 1.2.1 + '@marsidev/react-turnstile': + specifier: ^1.2.0 + version: 1.2.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@preact/signals-react': + specifier: ^3.2.1 + version: 3.2.1(react@19.1.1) + '@radix-ui/react-checkbox': + specifier: ^1.3.2 + version: 1.3.2(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-context-menu': + specifier: ^2.2.15 + version: 2.2.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.15 + version: 2.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-switch': + specifier: ^1.2.5 + version: 1.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-tabs': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.7 + version: 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tailwindcss/postcss': + specifier: ^4.1.11 + version: 4.1.11 + '@tanstack/react-virtual': + specifier: ^3.13.12 + version: 3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@xterm/addon-fit': + specifier: ^0.10.0 + version: 0.10.0(@xterm/xterm@5.5.0) + '@xterm/addon-search': + specifier: ^0.15.0 + version: 0.15.0(@xterm/xterm@5.5.0) + '@xterm/addon-web-links': + specifier: ^0.11.0 + version: 0.11.0(@xterm/xterm@5.5.0) + '@xterm/xterm': + specifier: ^5.5.0 + version: 5.5.0 + axios: + specifier: ^1.11.0 + version: 1.11.0 + browserslist: + specifier: ^4.25.1 + version: 4.25.1 + chart.js: + specifier: ^4.5.0 + version: 4.5.0 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + copy-to-clipboard: + specifier: ^3.3.3 + version: 3.3.3 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + debounce: + specifier: ^2.2.0 + version: 2.2.0 + deepmerge-ts: + specifier: ^7.1.5 + version: 7.1.5 + easy-peasy: + specifier: ^6.1.0 + version: 6.1.0(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + eslint-plugin-turbo: + specifier: ^2.5.5 + version: 2.5.5(eslint@9.32.0(jiti@2.4.2))(turbo@2.5.5) + events: + specifier: ^3.3.0 + version: 3.3.0 + formik: + specifier: ^2.4.6 + version: 2.4.6(react@19.1.1) + globals: + specifier: ^16.3.0 + version: 16.3.0 + install: + specifier: ^0.13.0 + version: 0.13.0 + laravel-vite-plugin: + specifier: ^2.0.0 + version: 2.0.0(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) + million: + specifier: ^3.1.11 + version: 3.1.11(rollup@4.46.2) + motion: + specifier: ^12.23.12 + version: 12.23.12(@emotion/is-prop-valid@1.2.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + pathe: + specifier: ^2.0.3 + version: 2.0.3 + qrcode.react: + specifier: ^4.2.0 + version: 4.2.0(react@19.1.1) + radix-ui: + specifier: ^1.4.2 + version: 1.4.2(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: + specifier: ^19.1.1 + version: 19.1.1 + react-chartjs-2: + specifier: ^5.3.0 + version: 5.3.0(chart.js@4.5.0)(react@19.1.1) + react-dom: + specifier: ^19.1.1 + version: 19.1.1(react@19.1.1) + react-fast-compare: + specifier: ^3.2.2 + version: 3.2.2 + react-router-dom: + specifier: ^7.7.1 + version: 7.7.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + sockette: + specifier: ^2.0.6 + version: 2.0.6 + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + styled-components: + specifier: ^6.1.19 + version: 6.1.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + swr: + specifier: ^2.3.4 + version: 2.3.4(react@19.1.1) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + tailwindcss: + specifier: ^4.1.11 + version: 4.1.11 + turbo: + specifier: ^2.5.5 + version: 2.5.5 + typescript-eslint: + specifier: ^8.39.0 + version: 8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2) + uuid: + specifier: ^11.1.0 + version: 11.1.0 + vite: + specifier: ^7.0.6 + version: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) + yup: + specifier: ^1.7.0 + version: 1.7.0 + devDependencies: + '@swc/core': + specifier: ^1.13.3 + version: 1.13.3(@swc/helpers@0.5.17) + '@swc/plugin-styled-components': + specifier: ^9.0.2 + version: 9.0.2 + '@trivago/prettier-plugin-sort-imports': + specifier: ^5.2.2 + version: 5.2.2(prettier@3.6.2) + '@types/debounce': + specifier: ^1.2.4 + version: 1.2.4 + '@types/events': + specifier: ^3.0.3 + version: 3.0.3 + '@types/node': + specifier: ^24.2.0 + version: 24.2.0 + '@types/react': + specifier: ^19.1.9 + version: 19.1.9 + '@types/react-copy-to-clipboard': + specifier: ^5.0.7 + version: 5.0.7 + '@types/react-dom': + specifier: ^19.1.7 + version: 19.1.7(@types/react@19.1.9) + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + '@vitejs/plugin-react-swc': + specifier: ^3.11.0 + version: 3.11.0(@swc/helpers@0.5.17)(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) + autoprefixer: + specifier: ^10.4.21 + version: 10.4.21(postcss@8.5.6) + cross-env: + specifier: ^10.0.0 + version: 10.0.0 + eslint: + specifier: ^9.32.0 + version: 9.32.0(jiti@2.4.2) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.32.0(jiti@2.4.2)) + eslint-plugin-prettier: + specifier: ^5.5.3 + version: 5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(prettier@3.6.2) + eslint-plugin-react: + specifier: ^7.37.5 + version: 7.37.5(eslint@9.32.0(jiti@2.4.2)) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.32.0(jiti@2.4.2)) + postcss: + specifier: ^8.5.6 + version: 8.5.6 + postcss-import: + specifier: ^16.1.1 + version: 16.1.1(postcss@8.5.6) + prettier: + specifier: ^3.6.2 + version: 3.6.2 + prettier-plugin-tailwindcss: + specifier: ^0.6.14 + version: 0.6.14(@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2))(prettier@3.6.2) + tailwind-scrollbar: + specifier: ^4.0.2 + version: 4.0.2(react@19.1.1)(tailwindcss@4.1.11) + ts-essentials: + specifier: ^10.1.1 + version: 10.1.1(typescript@5.9.2) + tw-animate-css: + specifier: ^1.3.6 + version: 1.3.6 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + vite-plugin-manifest-sri: + specifier: ^0.2.0 + version: 0.2.0 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.27.5': + resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.27.4': + resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.27.5': + resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.6': + resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.27.6': + resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.4': + resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + engines: {node: '>=6.9.0'} + + '@codemirror/autocomplete@6.18.6': + resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} + + '@codemirror/commands@6.8.1': + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} + + '@codemirror/lang-angular@0.1.4': + resolution: {integrity: sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==} + + '@codemirror/lang-cpp@6.0.2': + resolution: {integrity: sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-go@6.0.1': + resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} + + '@codemirror/lang-html@6.4.9': + resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==} + + '@codemirror/lang-java@6.0.1': + resolution: {integrity: sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==} + + '@codemirror/lang-javascript@6.2.4': + resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==} + + '@codemirror/lang-json@6.0.1': + resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==} + + '@codemirror/lang-less@6.0.2': + resolution: {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==} + + '@codemirror/lang-liquid@6.2.3': + resolution: {integrity: sha512-yeN+nMSrf/lNii3FJxVVEGQwFG0/2eDyH6gNOj+TGCa0hlNO4bhQnoO5ISnd7JOG+7zTEcI/GOoyraisFVY7jQ==} + + '@codemirror/lang-markdown@6.3.3': + resolution: {integrity: sha512-1fn1hQAPWlSSMCvnF810AkhWpNLkJpl66CRfIy3vVl20Sl4NwChkorCHqpMtNbXr1EuMJsrDnhEpjZxKZ2UX3A==} + + '@codemirror/lang-php@6.0.1': + resolution: {integrity: sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==} + + '@codemirror/lang-python@6.2.1': + resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==} + + '@codemirror/lang-rust@6.0.1': + resolution: {integrity: sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==} + + '@codemirror/lang-sass@6.0.2': + resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==} + + '@codemirror/lang-sql@6.9.0': + resolution: {integrity: sha512-xmtpWqKSgum1B1J3Ro6rf7nuPqf2+kJQg5SjrofCAcyCThOe0ihSktSoXfXuhQBnwx1QbmreBbLJM5Jru6zitg==} + + '@codemirror/lang-vue@0.1.3': + resolution: {integrity: sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==} + + '@codemirror/lang-wast@6.0.2': + resolution: {integrity: sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==} + + '@codemirror/lang-xml@6.1.0': + resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==} + + '@codemirror/lang-yaml@6.1.2': + resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} + + '@codemirror/language-data@6.5.1': + resolution: {integrity: sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==} + + '@codemirror/language@6.11.2': + resolution: {integrity: sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==} + + '@codemirror/legacy-modes@6.5.1': + resolution: {integrity: sha512-DJYQQ00N1/KdESpZV7jg9hafof/iBNp9h7TYo1SLMk86TWl9uDsVdho2dzd81K+v4retmK6mdC7WpuOQDytQqw==} + + '@codemirror/lint@6.8.5': + resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==} + + '@codemirror/search@6.5.11': + resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/view@6.38.1': + resolution: {integrity: sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==} + + '@date-fns/tz@1.3.1': + resolution: {integrity: sha512-LnBOyuj+piItX/D5BWBSckBsuZyOt7Jg2obGNiObq7qjl1A2/8F+i4RS8/MmkSdnw6hOe6afrJLCWrUWZw5Mlw==} + + '@emotion/is-prop-valid@1.2.2': + resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} + + '@emotion/memoize@0.8.1': + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + + '@esbuild/aix-ppc64@0.25.8': + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.8': + resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.8': + resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.8': + resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.8': + resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.8': + resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.8': + resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.8': + resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.8': + resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.8': + resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.8': + resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.8': + resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.8': + resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.8': + resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.8': + resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.8': + resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.8': + resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.8': + resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.8': + resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.8': + resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.8': + resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.8': + resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.8': + resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.8': + resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.8': + resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.8': + resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/compat@1.3.1': + resolution: {integrity: sha512-k8MHony59I5EPic6EQTCNOuPoVBnoYXkP+20xvwFjN7t0qI3ImyvyBgg+hIVPwC8JaxVjjUZld+cLfBLFDLucg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.40 || 9 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.32.0': + resolution: {integrity: sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.4': + resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.7.1': + resolution: {integrity: sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==} + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.1': + resolution: {integrity: sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==} + + '@floating-ui/dom@1.7.3': + resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==} + + '@floating-ui/react-dom@2.1.3': + resolution: {integrity: sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react-dom@2.1.5': + resolution: {integrity: sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.26.28': + resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@floating-ui/utils@0.2.9': + resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + + '@hcaptcha/loader@2.0.0': + resolution: {integrity: sha512-fFQH6ApU/zCCl6Y1bnbsxsp1Er/lKX+qlgljrpWDeFcenpEtoP68hExlKSXECospzKLeSWcr06cbTjlR/x3IJA==} + + '@hcaptcha/react-hcaptcha@1.12.0': + resolution: {integrity: sha512-QiHnQQ52k8SJJSHkc3cq4TlYzag7oPd4f5ZqnjVSe4fJDSlZaOQFtu5F5AYisVslwaitdDELPVLRsRJxiiI0Aw==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + + '@headlessui/react@2.2.7': + resolution: {integrity: sha512-WKdTymY8Y49H8/gUc/lIyYK1M+/6dq0Iywh4zTZVAaiTDprRfioxSgD0wnXTQTBpjpGJuTL1NO/mqEvc//5SSg==} + engines: {node: '>=10'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/cpp@1.1.3': + resolution: {integrity: sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==} + + '@lezer/css@1.2.1': + resolution: {integrity: sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==} + + '@lezer/go@1.0.1': + resolution: {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/html@1.3.10': + resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==} + + '@lezer/java@1.1.3': + resolution: {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==} + + '@lezer/javascript@1.5.1': + resolution: {integrity: sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==} + + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lezer/markdown@1.4.3': + resolution: {integrity: sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==} + + '@lezer/php@1.0.2': + resolution: {integrity: sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==} + + '@lezer/python@1.1.18': + resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==} + + '@lezer/rust@1.0.2': + resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==} + + '@lezer/sass@1.1.0': + resolution: {integrity: sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==} + + '@lezer/xml@1.0.6': + resolution: {integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==} + + '@lezer/yaml@1.0.3': + resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + + '@marsidev/react-turnstile@1.2.0': + resolution: {integrity: sha512-UQ8AF7RFSq2zaoEjC9rcMtF8WOEQW2wDDg+QcMojci/pM37oUaX8n+xIJ9kTXfAOkrEYGvmlnFr/JkREKeA2Yw==} + peerDependencies: + react: ^17.0.2 || ^18.0.0 || ^19.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@preact/signals-core@1.11.0': + resolution: {integrity: sha512-jglbibeWHuFRzEWVFY/TT7wB1PppJxmcSfUHcK+2J9vBRtiooMfw6tAPttojNYrrpdGViqAYCbPpmWYlMm+eMQ==} + + '@preact/signals-react@3.2.1': + resolution: {integrity: sha512-kRaWeKpT4tUKfcvUB0QldqQ1p/Xjg6cXDN3ubdVwnRrCie+95IXcaOaRHSLiAPgxwdOQYrgEjHe6ms46FtzQhQ==} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.11': + resolution: {integrity: sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.14': + resolution: {integrity: sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.2': + resolution: {integrity: sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.11': + resolution: {integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.2.15': + resolution: {integrity: sha512-UsQUMjcYTsBjTSXw0P3GO0werEQvUY2plgRQuKoCTtkNr45q1DiL51j4m7gxhABzZ0BadoXNsIbg7F3KwiUBbw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.14': + resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.15': + resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-form@0.1.7': + resolution: {integrity: sha512-IXLKFnaYvFg/KkeV5QfOX7tRnwHXp127koOFUjLWMTrRv5Rny3DQcAtIFFeA/Cli4HHM8DuJCXAUsgnFVJndlw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.14': + resolution: {integrity: sha512-CPYZ24Mhirm+g6D8jArmLzjYu4Eyg3TTUHswR26QgzXBHBe64BO/RHOJKzmF/Dxb4y4f9PKyJdwm/O/AhNkb+Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.15': + resolution: {integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menubar@1.1.15': + resolution: {integrity: sha512-Z71C7LGD+YDYo3TV81paUs8f3Zbmkvg6VLRQpKYfzioOE6n7fOhA3ApK/V/2Odolxjoc4ENk8AYCjohCNayd5A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.13': + resolution: {integrity: sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.1.7': + resolution: {integrity: sha512-w1vm7AGI8tNXVovOK7TYQHrAGpRF7qQL+ENpT1a743De5Zmay2RbWGKAiYDKIyIuqptns+znCKwNztE2xl1n0Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.2': + resolution: {integrity: sha512-F90uYnlBsLPU1UbSLciLsWQmk8+hdWa6SFw4GXaIdNWxFxI5ITKVdAG64f+Twaa9ic6xE7pqxPyUmodrGjT4pQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.14': + resolution: {integrity: sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.7': + resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.7': + resolution: {integrity: sha512-9w5XhD0KPOrm92OTTE0SysH3sYzHsSTHNvZgUBo/VZ80VdYyB5RneDbc0dKpURS24IxkoFRu/hI0i4XyfFwY6g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.10': + resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.9': + resolution: {integrity: sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.5': + resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.3.5': + resolution: {integrity: sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.5': + resolution: {integrity: sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.12': + resolution: {integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.14': + resolution: {integrity: sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.10': + resolution: {integrity: sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.9': + resolution: {integrity: sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.10': + resolution: {integrity: sha512-jiwQsduEL++M4YBIurjSa+voD86OIytCod0/dbIxFZDLD8NfO1//keXYMfsW8BPcfqwoNjt+y06XcJqAb4KR7A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.7': + resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@react-aria/focus@3.21.0': + resolution: {integrity: sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/interactions@3.25.4': + resolution: {integrity: sha512-HBQMxgUPHrW8V63u9uGgBymkMfj6vdWbB0GgUJY49K9mBKMsypcHeWkWM6+bF7kxRO728/IK8bWDV6whDbqjHg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/ssr@3.9.10': + resolution: {integrity: sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==} + engines: {node: '>= 12'} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/utils@3.30.0': + resolution: {integrity: sha512-ydA6y5G1+gbem3Va2nczj/0G0W7/jUVo/cbN10WA5IizzWIwMP5qhFr7macgbKfHMkZ+YZC3oXnt2NNre5odKw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-stately/flags@3.1.2': + resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==} + + '@react-stately/utils@3.10.8': + resolution: {integrity: sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-types/shared@3.31.0': + resolution: {integrity: sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.46.2': + resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.46.2': + resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.46.2': + resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.46.2': + resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.46.2': + resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.46.2': + resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.46.2': + resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.46.2': + resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.46.2': + resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.46.2': + resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.46.2': + resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.46.2': + resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.46.2': + resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} + cpu: [x64] + os: [win32] + + '@swc/core-darwin-arm64@1.13.3': + resolution: {integrity: sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.13.3': + resolution: {integrity: sha512-p0X6yhxmNUOMZrbeZ3ZNsPige8lSlSe1llllXvpCLkKKxN/k5vZt1sULoq6Nj4eQ7KeHQVm81/+AwKZyf/e0TA==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.13.3': + resolution: {integrity: sha512-OmDoiexL2fVWvQTCtoh0xHMyEkZweQAlh4dRyvl8ugqIPEVARSYtaj55TBMUJIP44mSUOJ5tytjzhn2KFxFcBA==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.13.3': + resolution: {integrity: sha512-STfKku3QfnuUj6k3g9ld4vwhtgCGYIFQmsGPPgT9MK/dI3Lwnpe5Gs5t1inoUIoGNP8sIOLlBB4HV4MmBjQuhw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.13.3': + resolution: {integrity: sha512-bc+CXYlFc1t8pv9yZJGus372ldzOVscBl7encUBlU1m/Sig0+NDJLz6cXXRcFyl6ABNOApWeR4Yl7iUWx6C8og==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.13.3': + resolution: {integrity: sha512-dFXoa0TEhohrKcxn/54YKs1iwNeW6tUkHJgXW33H381SvjKFUV53WR231jh1sWVJETjA3vsAwxKwR23s7UCmUA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.13.3': + resolution: {integrity: sha512-ieyjisLB+ldexiE/yD8uomaZuZIbTc8tjquYln9Quh5ykOBY7LpJJYBWvWtm1g3pHv6AXlBI8Jay7Fffb6aLfA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.13.3': + resolution: {integrity: sha512-elTQpnaX5vESSbhCEgcwXjpMsnUbqqHfEpB7ewpkAsLzKEXZaK67ihSRYAuAx6ewRQTo7DS5iTT6X5aQD3MzMw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.13.3': + resolution: {integrity: sha512-nvehQVEOdI1BleJpuUgPLrclJ0TzbEMc+MarXDmmiRFwEUGqj+pnfkTSb7RZyS1puU74IXdK/YhTirHurtbI9w==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.13.3': + resolution: {integrity: sha512-A+JSKGkRbPLVV2Kwx8TaDAV0yXIXm/gc8m98hSkVDGlPBBmydgzNdWy3X7HTUBM7IDk7YlWE7w2+RUGjdgpTmg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.13.3': + resolution: {integrity: sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + + '@swc/plugin-styled-components@9.0.2': + resolution: {integrity: sha512-H9U124CME/JeZfsxnLcTs5adRXSEfDqlwYfzcgrhcUN1L/egi4zohvol0OyhtQJVhDBJ2gFKokxvf7EyRc8Sbg==} + + '@swc/types@0.1.23': + resolution: {integrity: sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==} + + '@tailwindcss/node@4.1.11': + resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} + + '@tailwindcss/oxide-android-arm64@4.1.11': + resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.11': + resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.11': + resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.11': + resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==} + + '@tanstack/react-virtual@3.13.12': + resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.13.12': + resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + + '@trivago/prettier-plugin-sort-imports@5.2.2': + resolution: {integrity: sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==} + engines: {node: '>18.12'} + peerDependencies: + '@vue/compiler-sfc': 3.x + prettier: 2.x - 3.x + prettier-plugin-svelte: 3.x + svelte: 4.x || 5.x + peerDependenciesMeta: + '@vue/compiler-sfc': + optional: true + prettier-plugin-svelte: + optional: true + svelte: + optional: true + + '@types/debounce@1.2.4': + resolution: {integrity: sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/events@3.0.3': + resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==} + + '@types/hoist-non-react-statics@3.3.6': + resolution: {integrity: sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.2.0': + resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==} + + '@types/prismjs@1.26.5': + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + + '@types/react-copy-to-clipboard@5.0.7': + resolution: {integrity: sha512-Gft19D+as4M+9Whq1oglhmK49vqPhcLzk8WfvfLvaYMIPYanyfLy0+CwFucMJfdKoSFyySPmkkWn8/E6voQXjQ==} + + '@types/react-dom@19.1.7': + resolution: {integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.1.9': + resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==} + + '@types/stylis@4.2.5': + resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + + '@typescript-eslint/eslint-plugin@8.39.0': + resolution: {integrity: sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.39.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.39.0': + resolution: {integrity: sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.39.0': + resolution: {integrity: sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.39.0': + resolution: {integrity: sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.39.0': + resolution: {integrity: sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.39.0': + resolution: {integrity: sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.39.0': + resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.39.0': + resolution: {integrity: sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.39.0': + resolution: {integrity: sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.39.0': + resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react-swc@3.11.0': + resolution: {integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==} + peerDependencies: + vite: ^4 || ^5 || ^6 || ^7 + + '@xterm/addon-fit@0.10.0': + resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/addon-search@0.15.0': + resolution: {integrity: sha512-ZBZKLQ+EuKE83CqCmSSz5y1tx+aNOCUaA7dm6emgOX+8J9H1FWXZyrKfzjwzV+V14TV3xToz1goIeRhXBS5qjg==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/addon-web-links@0.11.0': + resolution: {integrity: sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/xterm@5.5.0': + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + + caniuse-lite@1.0.30001723: + resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==} + + caniuse-lite@1.0.30001727: + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chart.js@4.5.0: + resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==} + engines: {pnpm: '>=8'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + + cross-env@10.0.0: + resolution: {integrity: sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==} + engines: {node: '>=20'} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + + debounce@2.2.0: + resolution: {integrity: sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==} + engines: {node: '>=18'} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + + deepmerge@2.2.1: + resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + easy-peasy@6.1.0: + resolution: {integrity: sha512-zW7mUATJRohNWWSrihmeFhS85jjsIHa1ptTDn6bSXa2DXh8Zeawfpmm1LtKa+Y6jNNz5aQlMjWZ8uuuVk5bdVQ==} + peerDependencies: + '@types/react': ^18.0 || ^19.0 + '@types/react-dom': ^18.0 || ^19.0 + react: ^18.0 || ^19.0 + react-dom: ^18.0 || ^19.0 + react-native: '>=0.59' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + react-dom: + optional: true + react-native: + optional: true + + electron-to-chromium@1.5.180: + resolution: {integrity: sha512-ED+GEyEh3kYMwt2faNmgMB0b8O5qtATGgR4RmRsIp4T6p7B8vdMbIedYndnvZfsaXvSzegtpfqRMDNCjjiSduA==} + + enhanced-resolve@5.18.2: + resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} + engines: {node: '>=10.13.0'} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.3: + resolution: {integrity: sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-plugin-turbo@2.5.5: + resolution: {integrity: sha512-IlN65X6W7rgK88u5xl1xC+7FIGKA7eyaca0yxZQ9CBNV6keAaqtjZQLw8ZfXdv7T+MzTLYkYOeOHAv8yCRUx4Q==} + peerDependencies: + eslint: '>6.6.0' + turbo: '>2.0.0' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.32.0: + resolution: {integrity: sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + formik@2.4.6: + resolution: {integrity: sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==} + peerDependencies: + react: '>=16.8.0' + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + framer-motion@12.23.12: + resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.3.0: + resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + install@0.13.0: + resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} + engines: {node: '>= 0.10'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + javascript-natural-sort@0.7.1: + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + laravel-vite-plugin@2.0.0: + resolution: {integrity: sha512-pnaKHInJgiWpG/g+LmaISHl7D/1s5wnOXnrGiBdt4NOs+tYZRw0v/ZANELGX2/dGgHyEzO+iZ6x4idpoK04z/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + vite: ^7.0.0 + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + million@3.1.11: + resolution: {integrity: sha512-6Vh1s0da0PzSqbbp9Zd8yMTIkOWnvBU4vNJCMHTZPXaY3fZ5h+N7s5croS/RBgjJIHz3WQZnvyNBQz7gQ6cqJg==} + hasBin: true + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + motion-dom@12.23.12: + resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==} + + motion-utils@12.23.6: + resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + + motion@12.23.12: + resolution: {integrity: sha512-8jCD8uW5GD1csOoqh1WhH1A6j5APHVE15nuBkFeRiMzYBdRwyAHmSP/oXSuW0WJPZRXTFdBoG4hY9TFWNhhwng==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-import@16.1.1: + resolution: {integrity: sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier-plugin-tailwindcss@0.6.14: + resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==} + engines: {node: '>=14.21.3'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + prism-react-renderer@2.4.1: + resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==} + peerDependencies: + react: '>=16.0.0' + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qrcode.react@4.2.0: + resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + radix-ui@1.4.2: + resolution: {integrity: sha512-fT/3YFPJzf2WUpqDoQi005GS8EpCi+53VhcLaHUj5fwkPYiZAjk1mSxFvbMA8Uq71L03n+WysuYC+mlKkXxt/Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + react-chartjs-2@5.3.0: + resolution: {integrity: sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==} + peerDependencies: + chart.js: ^4.1.1 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-dom@19.1.1: + resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} + peerDependencies: + react: ^19.1.1 + + react-fast-compare@2.0.4: + resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==} + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-router-dom@7.7.1: + resolution: {integrity: sha512-bavdk2BA5r3MYalGKZ01u8PGuDBloQmzpBZVhDLrOOv1N943Wq6dcM9GhB3x8b7AbqPMEezauv4PeGkAJfy7FQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.7.1: + resolution: {integrity: sha512-jVKHXoWRIsD/qS6lvGveckwb862EekvapdHJN/cGmzw40KnJH5gg53ujOJ4qX6EKIK9LSBfFed/xiQ5yeXNrUA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.1.1: + resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.46.2: + resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + sockette@2.0.6: + resolution: {integrity: sha512-W6iG8RGV6Zife3Cj+FhuyHV447E6fqFM2hKmnaQrTvg3OydINV3Msj3WPFbX76blUlUxvQSMMMdrJxce8NqI5Q==} + + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + + styled-components@6.1.19: + resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==} + engines: {node: '>= 16'} + peerDependencies: + react: '>= 16.8.0' + react-dom: '>= 16.8.0' + + stylis@4.3.2: + resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swr@2.3.4: + resolution: {integrity: sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + + tailwind-scrollbar@4.0.2: + resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==} + engines: {node: '>=12.13.0'} + peerDependencies: + tailwindcss: 4.x + + tailwindcss@4.1.11: + resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} + + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + + tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + + toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-essentials@10.1.1: + resolution: {integrity: sha512-4aTB7KLHKmUvkjNj8V+EdnmuVTiECzn3K+zIbRthumvHu+j44x3w63xpfs0JL3NGIzGXqoQ7AV591xHO+XrOTw==} + peerDependencies: + typescript: '>=4.5.0' + peerDependenciesMeta: + typescript: + optional: true + + ts-toolbelt@9.6.0: + resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} + + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + turbo-darwin-64@2.5.5: + resolution: {integrity: sha512-RYnTz49u4F5tDD2SUwwtlynABNBAfbyT2uU/brJcyh5k6lDLyNfYKdKmqd3K2ls4AaiALWrFKVSBsiVwhdFNzQ==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.5.5: + resolution: {integrity: sha512-Tk+ZeSNdBobZiMw9aFypQt0DlLsWSFWu1ymqsAdJLuPoAH05qCfYtRxE1pJuYHcJB5pqI+/HOxtJoQ40726Btw==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.5.5: + resolution: {integrity: sha512-2/XvMGykD7VgsvWesZZYIIVXMlgBcQy+ZAryjugoTcvJv8TZzSU/B1nShcA7IAjZ0q7OsZ45uP2cOb8EgKT30w==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.5.5: + resolution: {integrity: sha512-DW+8CjCjybu0d7TFm9dovTTVg1VRnlkZ1rceO4zqsaLrit3DgHnN4to4uwyuf9s2V/BwS3IYcRy+HG9BL596Iw==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.5.5: + resolution: {integrity: sha512-q5p1BOy8ChtSZfULuF1BhFMYIx6bevXu4fJ+TE/hyNfyHJIfjl90Z6jWdqAlyaFLmn99X/uw+7d6T/Y/dr5JwQ==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.5.5: + resolution: {integrity: sha512-AXbF1KmpHUq3PKQwddMGoKMYhHsy5t1YBQO8HZ04HLMR0rWv9adYlQ8kaeQJTko1Ay1anOBFTqaxfVOOsu7+1Q==} + cpu: [arm64] + os: [win32] + + turbo@2.5.5: + resolution: {integrity: sha512-eZ7wI6KjtT1eBqCnh2JPXWNUAxtoxxfi6VdBdZFvil0ychCOTxbm7YLRBi1JSt7U3c+u3CLxpoPxLdvr/Npr3A==} + hasBin: true + + tw-animate-css@1.3.6: + resolution: {integrity: sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.39.0: + resolution: {integrity: sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + + undici@6.21.3: + resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} + engines: {node: '>=18.17'} + + unplugin@1.16.1: + resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} + engines: {node: '>=14.0.0'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + vite-plugin-full-reload@1.2.0: + resolution: {integrity: sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==} + + vite-plugin-manifest-sri@0.2.0: + resolution: {integrity: sha512-Zt5jt19xTIJ91LOuQTCtNG7rTFc5OziAjBz2H5NdCGqaOD1nxrWExLhcKW+W4/q8/jOPCg/n5ncYEQmqCxiGQQ==} + + vite@7.0.6: + resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yup@1.7.0: + resolution: {integrity: sha512-VJce62dBd+JQvoc+fCVq+KZfPHr+hXaxCcVgotfwWvlR0Ja3ffYKaJBT8rptPOSKOGJDCUnW2C2JWpud7aRP6Q==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.27.5': {} + + '@babel/core@7.27.4': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.27.5': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.27.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.27.6': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/runtime@7.27.6': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + + '@babel/traverse@7.27.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + debug: 4.4.1 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@codemirror/autocomplete@6.18.6': + dependencies: + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.8.1': + dependencies: + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + + '@codemirror/lang-angular@0.1.4': + dependencies: + '@codemirror/lang-html': 6.4.9 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-cpp@6.0.2': + dependencies: + '@codemirror/language': 6.11.2 + '@lezer/cpp': 1.1.3 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/css': 1.2.1 + + '@codemirror/lang-go@6.0.1': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/go': 1.0.1 + + '@codemirror/lang-html@6.4.9': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + '@lezer/css': 1.2.1 + '@lezer/html': 1.3.10 + + '@codemirror/lang-java@6.0.1': + dependencies: + '@codemirror/language': 6.11.2 + '@lezer/java': 1.1.3 + + '@codemirror/lang-javascript@6.2.4': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.2 + '@codemirror/lint': 6.8.5 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + '@lezer/javascript': 1.5.1 + + '@codemirror/lang-json@6.0.1': + dependencies: + '@codemirror/language': 6.11.2 + '@lezer/json': 1.0.3 + + '@codemirror/lang-less@6.0.2': + dependencies: + '@codemirror/lang-css': 6.3.1 + '@codemirror/language': 6.11.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-liquid@6.2.3': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/lang-html': 6.4.9 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-markdown@6.3.3': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/lang-html': 6.4.9 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + '@lezer/markdown': 1.4.3 + + '@codemirror/lang-php@6.0.1': + dependencies: + '@codemirror/lang-html': 6.4.9 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/php': 1.0.2 + + '@codemirror/lang-python@6.2.1': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/python': 1.1.18 + + '@codemirror/lang-rust@6.0.1': + dependencies: + '@codemirror/language': 6.11.2 + '@lezer/rust': 1.0.2 + + '@codemirror/lang-sass@6.0.2': + dependencies: + '@codemirror/lang-css': 6.3.1 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/sass': 1.1.0 + + '@codemirror/lang-sql@6.9.0': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-vue@0.1.3': + dependencies: + '@codemirror/lang-html': 6.4.9 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-wast@6.0.2': + dependencies: + '@codemirror/language': 6.11.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-xml@6.1.0': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + '@lezer/xml': 1.0.6 + + '@codemirror/lang-yaml@6.1.2': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + '@lezer/yaml': 1.0.3 + + '@codemirror/language-data@6.5.1': + dependencies: + '@codemirror/lang-angular': 0.1.4 + '@codemirror/lang-cpp': 6.0.2 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-go': 6.0.1 + '@codemirror/lang-html': 6.4.9 + '@codemirror/lang-java': 6.0.1 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/lang-json': 6.0.1 + '@codemirror/lang-less': 6.0.2 + '@codemirror/lang-liquid': 6.2.3 + '@codemirror/lang-markdown': 6.3.3 + '@codemirror/lang-php': 6.0.1 + '@codemirror/lang-python': 6.2.1 + '@codemirror/lang-rust': 6.0.1 + '@codemirror/lang-sass': 6.0.2 + '@codemirror/lang-sql': 6.9.0 + '@codemirror/lang-vue': 0.1.3 + '@codemirror/lang-wast': 6.0.2 + '@codemirror/lang-xml': 6.1.0 + '@codemirror/lang-yaml': 6.1.2 + '@codemirror/language': 6.11.2 + '@codemirror/legacy-modes': 6.5.1 + + '@codemirror/language@6.11.2': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/legacy-modes@6.5.1': + dependencies: + '@codemirror/language': 6.11.2 + + '@codemirror/lint@6.8.5': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + crelt: 1.0.6 + + '@codemirror/search@6.5.11': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.38.1': + dependencies: + '@codemirror/state': 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + + '@date-fns/tz@1.3.1': {} + + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + + '@emotion/memoize@0.8.1': {} + + '@emotion/unitless@0.8.1': {} + + '@epic-web/invariant@1.0.0': {} + + '@esbuild/aix-ppc64@0.25.8': + optional: true + + '@esbuild/android-arm64@0.25.8': + optional: true + + '@esbuild/android-arm@0.25.8': + optional: true + + '@esbuild/android-x64@0.25.8': + optional: true + + '@esbuild/darwin-arm64@0.25.8': + optional: true + + '@esbuild/darwin-x64@0.25.8': + optional: true + + '@esbuild/freebsd-arm64@0.25.8': + optional: true + + '@esbuild/freebsd-x64@0.25.8': + optional: true + + '@esbuild/linux-arm64@0.25.8': + optional: true + + '@esbuild/linux-arm@0.25.8': + optional: true + + '@esbuild/linux-ia32@0.25.8': + optional: true + + '@esbuild/linux-loong64@0.25.8': + optional: true + + '@esbuild/linux-mips64el@0.25.8': + optional: true + + '@esbuild/linux-ppc64@0.25.8': + optional: true + + '@esbuild/linux-riscv64@0.25.8': + optional: true + + '@esbuild/linux-s390x@0.25.8': + optional: true + + '@esbuild/linux-x64@0.25.8': + optional: true + + '@esbuild/netbsd-arm64@0.25.8': + optional: true + + '@esbuild/netbsd-x64@0.25.8': + optional: true + + '@esbuild/openbsd-arm64@0.25.8': + optional: true + + '@esbuild/openbsd-x64@0.25.8': + optional: true + + '@esbuild/openharmony-arm64@0.25.8': + optional: true + + '@esbuild/sunos-x64@0.25.8': + optional: true + + '@esbuild/win32-arm64@0.25.8': + optional: true + + '@esbuild/win32-ia32@0.25.8': + optional: true + + '@esbuild/win32-x64@0.25.8': + optional: true + + '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0(jiti@2.4.2))': + dependencies: + eslint: 9.32.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/compat@1.3.1(eslint@9.32.0(jiti@2.4.2))': + optionalDependencies: + eslint: 9.32.0(jiti@2.4.2) + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.0': {} + + '@eslint/core@0.15.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.32.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.4': + dependencies: + '@eslint/core': 0.15.1 + levn: 0.4.1 + + '@floating-ui/core@1.7.1': + dependencies: + '@floating-ui/utils': 0.2.9 + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.1': + dependencies: + '@floating-ui/core': 1.7.1 + '@floating-ui/utils': 0.2.9 + + '@floating-ui/dom@1.7.3': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/dom': 1.7.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@floating-ui/react-dom@2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/dom': 1.7.3 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@floating-ui/react@0.26.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/react-dom': 2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@floating-ui/utils': 0.2.10 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + tabbable: 6.2.0 + + '@floating-ui/utils@0.2.10': {} + + '@floating-ui/utils@0.2.9': {} + + '@hcaptcha/loader@2.0.0': {} + + '@hcaptcha/react-hcaptcha@1.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@hcaptcha/loader': 2.0.0 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@headlessui/react@2.2.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/react': 0.26.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/react-virtual': 3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + use-sync-external-store: 1.5.0(react@19.1.1) + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.4': {} + + '@jridgewell/trace-mapping@0.3.29': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + + '@kurkle/color@0.3.4': {} + + '@lezer/common@1.2.3': {} + + '@lezer/cpp@1.1.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/css@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/go@1.0.1': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/html@1.3.10': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/java@1.1.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/javascript@1.5.1': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/json@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/markdown@1.4.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + + '@lezer/php@1.0.2': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/python@1.1.18': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/rust@1.0.2': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/sass@1.1.0': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/xml@1.0.6': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/yaml@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@marijn/find-cluster-break@1.0.2': {} + + '@marsidev/react-turnstile@1.2.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@pkgr/core@0.2.9': {} + + '@preact/signals-core@1.11.0': {} + + '@preact/signals-react@3.2.1(react@19.1.1)': + dependencies: + '@preact/signals-core': 1.11.0 + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.2': {} + + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-accordion@1.2.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collapsible': 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-checkbox@1.3.2(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-context-menu@2.2.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-context@1.1.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-direction@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-form@0.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-hover-card@1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-id@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-menu@2.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-menubar@1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-navigation-menu@1.2.13(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-one-time-password-field@0.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-password-toggle-field@0.1.2(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/react-dom': 2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/rect': 1.1.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-radio-group@1.3.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-select@2.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-slider@1.3.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-switch@1.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-toast@1.2.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-toggle-group@1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toggle': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-toggle@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-toolbar@1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toggle-group': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/rect@1.1.1': {} + + '@react-aria/focus@3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@swc/helpers': 0.5.17 + clsx: 2.1.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@react-aria/interactions@3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@react-aria/ssr': 3.9.10(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/flags': 3.1.2 + '@react-types/shared': 3.31.0(react@19.1.1) + '@swc/helpers': 0.5.17 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@react-aria/ssr@3.9.10(react@19.1.1)': + dependencies: + '@swc/helpers': 0.5.17 + react: 19.1.1 + + '@react-aria/utils@3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@react-aria/ssr': 3.9.10(react@19.1.1) + '@react-stately/flags': 3.1.2 + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@swc/helpers': 0.5.17 + clsx: 2.1.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@react-stately/flags@3.1.2': + dependencies: + '@swc/helpers': 0.5.17 + + '@react-stately/utils@3.10.8(react@19.1.1)': + dependencies: + '@swc/helpers': 0.5.17 + react: 19.1.1 + + '@react-types/shared@3.31.0(react@19.1.1)': + dependencies: + react: 19.1.1 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/pluginutils@5.2.0(rollup@4.46.2)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.46.2 + + '@rollup/rollup-android-arm-eabi@4.46.2': + optional: true + + '@rollup/rollup-android-arm64@4.46.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.46.2': + optional: true + + '@rollup/rollup-darwin-x64@4.46.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.46.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.46.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.46.2': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.46.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.46.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.46.2': + optional: true + + '@swc/core-darwin-arm64@1.13.3': + optional: true + + '@swc/core-darwin-x64@1.13.3': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.13.3': + optional: true + + '@swc/core-linux-arm64-gnu@1.13.3': + optional: true + + '@swc/core-linux-arm64-musl@1.13.3': + optional: true + + '@swc/core-linux-x64-gnu@1.13.3': + optional: true + + '@swc/core-linux-x64-musl@1.13.3': + optional: true + + '@swc/core-win32-arm64-msvc@1.13.3': + optional: true + + '@swc/core-win32-ia32-msvc@1.13.3': + optional: true + + '@swc/core-win32-x64-msvc@1.13.3': + optional: true + + '@swc/core@1.13.3(@swc/helpers@0.5.17)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.23 + optionalDependencies: + '@swc/core-darwin-arm64': 1.13.3 + '@swc/core-darwin-x64': 1.13.3 + '@swc/core-linux-arm-gnueabihf': 1.13.3 + '@swc/core-linux-arm64-gnu': 1.13.3 + '@swc/core-linux-arm64-musl': 1.13.3 + '@swc/core-linux-x64-gnu': 1.13.3 + '@swc/core-linux-x64-musl': 1.13.3 + '@swc/core-win32-arm64-msvc': 1.13.3 + '@swc/core-win32-ia32-msvc': 1.13.3 + '@swc/core-win32-x64-msvc': 1.13.3 + '@swc/helpers': 0.5.17 + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + + '@swc/plugin-styled-components@9.0.2': + dependencies: + '@swc/counter': 0.1.3 + + '@swc/types@0.1.23': + dependencies: + '@swc/counter': 0.1.3 + + '@tailwindcss/node@4.1.11': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.2 + jiti: 2.4.2 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.11 + + '@tailwindcss/oxide-android-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide@4.1.11': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-x64': 4.1.11 + '@tailwindcss/oxide-freebsd-x64': 4.1.11 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.11 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.11 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-x64-musl': 4.1.11 + '@tailwindcss/oxide-wasm32-wasi': 4.1.11 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 + + '@tailwindcss/postcss@4.1.11': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.11 + '@tailwindcss/oxide': 4.1.11 + postcss: 8.5.6 + tailwindcss: 4.1.11 + + '@tanstack/react-virtual@3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@tanstack/virtual-core': 3.13.12 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@tanstack/virtual-core@3.13.12': {} + + '@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2)': + dependencies: + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + javascript-natural-sort: 0.7.1 + lodash: 4.17.21 + prettier: 3.6.2 + transitivePeerDependencies: + - supports-color + + '@types/debounce@1.2.4': {} + + '@types/estree@1.0.8': {} + + '@types/events@3.0.3': {} + + '@types/hoist-non-react-statics@3.3.6': + dependencies: + '@types/react': 19.1.9 + hoist-non-react-statics: 3.3.2 + + '@types/json-schema@7.0.15': {} + + '@types/node@24.2.0': + dependencies: + undici-types: 7.10.0 + + '@types/prismjs@1.26.5': {} + + '@types/react-copy-to-clipboard@5.0.7': + dependencies: + '@types/react': 19.1.9 + + '@types/react-dom@19.1.7(@types/react@19.1.9)': + dependencies: + '@types/react': 19.1.9 + + '@types/react@19.1.9': + dependencies: + csstype: 3.1.3 + + '@types/stylis@4.2.5': {} + + '@types/uuid@10.0.0': {} + + '@typescript-eslint/eslint-plugin@8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.39.0 + '@typescript-eslint/type-utils': 8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/utils': 8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.39.0 + eslint: 9.32.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.39.0 + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.39.0 + debug: 4.4.1 + eslint: 9.32.0(jiti@2.4.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.39.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) + '@typescript-eslint/types': 8.39.0 + debug: 4.4.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.39.0': + dependencies: + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/visitor-keys': 8.39.0 + + '@typescript-eslint/tsconfig-utils@8.39.0(typescript@5.9.2)': + dependencies: + typescript: 5.9.2 + + '@typescript-eslint/type-utils@8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2) + debug: 4.4.1 + eslint: 9.32.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.39.0': {} + + '@typescript-eslint/typescript-estree@8.39.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/project-service': 8.39.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/visitor-keys': 8.39.0 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.39.0 + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) + eslint: 9.32.0(jiti@2.4.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.39.0': + dependencies: + '@typescript-eslint/types': 8.39.0 + eslint-visitor-keys: 4.2.1 + + '@vitejs/plugin-react-swc@3.11.0(@swc/helpers@0.5.17)(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.27 + '@swc/core': 1.13.3(@swc/helpers@0.5.17) + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) + transitivePeerDependencies: + - '@swc/helpers' + + '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-search@0.15.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-web-links@0.11.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/xterm@5.5.0': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + async-function@1.0.0: {} + + asynckit@0.4.0: {} + + autoprefixer@10.4.21(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + caniuse-lite: 1.0.30001723 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@1.11.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001727 + electron-to-chromium: 1.5.180 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelize@1.0.1: {} + + caniuse-lite@1.0.30001723: {} + + caniuse-lite@1.0.30001727: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chart.js@4.5.0: + dependencies: + '@kurkle/color': 0.3.4 + + chownr@3.0.0: {} + + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.0.2: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + crelt@1.0.6: {} + + cross-env@10.0.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-color-keywords@1.0.0: {} + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + + csstype@3.1.3: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + date-fns@4.1.0: {} + + debounce@2.2.0: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + deepmerge-ts@7.1.5: {} + + deepmerge@2.2.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.0.4: {} + + detect-node-es@1.1.0: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dotenv@16.0.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + easy-peasy@6.1.0(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@babel/runtime': 7.27.6 + fast-deep-equal: 3.1.3 + immer: 9.0.21 + react: 19.1.1 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + ts-toolbelt: 9.6.0 + use-sync-external-store: 1.5.0(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + react-dom: 19.1.1(react@19.1.1) + + electron-to-chromium@1.5.180: {} + + enhanced-resolve@5.18.2: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.25.8: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.8 + '@esbuild/android-arm': 0.25.8 + '@esbuild/android-arm64': 0.25.8 + '@esbuild/android-x64': 0.25.8 + '@esbuild/darwin-arm64': 0.25.8 + '@esbuild/darwin-x64': 0.25.8 + '@esbuild/freebsd-arm64': 0.25.8 + '@esbuild/freebsd-x64': 0.25.8 + '@esbuild/linux-arm': 0.25.8 + '@esbuild/linux-arm64': 0.25.8 + '@esbuild/linux-ia32': 0.25.8 + '@esbuild/linux-loong64': 0.25.8 + '@esbuild/linux-mips64el': 0.25.8 + '@esbuild/linux-ppc64': 0.25.8 + '@esbuild/linux-riscv64': 0.25.8 + '@esbuild/linux-s390x': 0.25.8 + '@esbuild/linux-x64': 0.25.8 + '@esbuild/netbsd-arm64': 0.25.8 + '@esbuild/netbsd-x64': 0.25.8 + '@esbuild/openbsd-arm64': 0.25.8 + '@esbuild/openbsd-x64': 0.25.8 + '@esbuild/openharmony-arm64': 0.25.8 + '@esbuild/sunos-x64': 0.25.8 + '@esbuild/win32-arm64': 0.25.8 + '@esbuild/win32-ia32': 0.25.8 + '@esbuild/win32-x64': 0.25.8 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.32.0(jiti@2.4.2)): + dependencies: + eslint: 9.32.0(jiti@2.4.2) + + eslint-plugin-prettier@5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(prettier@3.6.2): + dependencies: + eslint: 9.32.0(jiti@2.4.2) + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.32.0(jiti@2.4.2)) + + eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)): + dependencies: + eslint: 9.32.0(jiti@2.4.2) + + eslint-plugin-react@7.37.5(eslint@9.32.0(jiti@2.4.2)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.32.0(jiti@2.4.2) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-plugin-turbo@2.5.5(eslint@9.32.0(jiti@2.4.2))(turbo@2.5.5): + dependencies: + dotenv: 16.0.3 + eslint: 9.32.0(jiti@2.4.2) + turbo: 2.5.5 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.32.0(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.15.1 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.32.0 + '@eslint/plugin-kit': 0.3.4 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + events@3.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.4.6(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formik@2.4.6(react@19.1.1): + dependencies: + '@types/hoist-non-react-statics': 3.3.6 + deepmerge: 2.2.1 + hoist-non-react-statics: 3.3.2 + lodash: 4.17.21 + lodash-es: 4.17.21 + react: 19.1.1 + react-fast-compare: 2.0.4 + tiny-warning: 1.0.3 + tslib: 2.8.1 + + fraction.js@4.3.7: {} + + framer-motion@12.23.12(@emotion/is-prop-valid@1.2.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + motion-dom: 12.23.12 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + '@emotion/is-prop-valid': 1.2.2 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@16.3.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + immer@9.0.21: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + install@0.13.0: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + javascript-natural-sort@0.7.1: {} + + jiti@2.4.2: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@4.1.5: {} + + laravel-vite-plugin@2.0.0(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)): + dependencies: + picocolors: 1.1.1 + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) + vite-plugin-full-reload: 1.2.0 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + million@3.1.11(rollup@4.46.2): + dependencies: + '@babel/core': 7.27.4 + '@babel/types': 7.27.6 + '@rollup/pluginutils': 5.2.0(rollup@4.46.2) + kleur: 4.1.5 + undici: 6.21.3 + unplugin: 1.16.1 + transitivePeerDependencies: + - rollup + - supports-color + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + motion-dom@12.23.12: + dependencies: + motion-utils: 12.23.6 + + motion-utils@12.23.6: {} + + motion@12.23.12(@emotion/is-prop-valid@1.2.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + framer-motion: 12.23.12(@emotion/is-prop-valid@1.2.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + tslib: 2.8.1 + optionalDependencies: + '@emotion/is-prop-valid': 1.2.2 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.19: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + possible-typed-array-names@1.1.0: {} + + postcss-import@16.1.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.10 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.49: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier-plugin-tailwindcss@0.6.14(@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2))(prettier@3.6.2): + dependencies: + prettier: 3.6.2 + optionalDependencies: + '@trivago/prettier-plugin-sort-imports': 5.2.2(prettier@3.6.2) + + prettier@3.6.2: {} + + prism-react-renderer@2.4.1(react@19.1.1): + dependencies: + '@types/prismjs': 1.26.5 + clsx: 2.1.1 + react: 19.1.1 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-expr@2.0.6: {} + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + qrcode.react@4.2.0(react@19.1.1): + dependencies: + react: 19.1.1 + + queue-microtask@1.2.3: {} + + radix-ui@1.4.2(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-accordion': 1.2.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-alert-dialog': 1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-checkbox': 1.3.2(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-collapsible': 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context-menu': 2.2.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-dropdown-menu': 2.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-form': 0.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-hover-card': 1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-menubar': 1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-navigation-menu': 1.2.13(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-one-time-password-field': 0.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-password-toggle-field': 0.1.2(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-popover': 1.1.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-radio-group': 1.3.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-scroll-area': 1.2.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-select': 2.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slider': 1.3.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-switch': 1.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-tabs': 1.1.12(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toast': 1.2.14(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toggle': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toggle-group': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toolbar': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-tooltip': 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + react-chartjs-2@5.3.0(chart.js@4.5.0)(react@19.1.1): + dependencies: + chart.js: 4.5.0 + react: 19.1.1 + + react-dom@19.1.1(react@19.1.1): + dependencies: + react: 19.1.1 + scheduler: 0.26.0 + + react-fast-compare@2.0.4: {} + + react-fast-compare@3.2.2: {} + + react-is@16.13.1: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + react-style-singleton: 2.2.3(@types/react@19.1.9)(react@19.1.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.9 + + react-remove-scroll@2.7.1(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.9)(react@19.1.1) + react-style-singleton: 2.2.3(@types/react@19.1.9)(react@19.1.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.9)(react@19.1.1) + use-sidecar: 1.1.3(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + + react-router-dom@7.7.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-router: 7.7.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + + react-router@7.7.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + cookie: 1.0.2 + react: 19.1.1 + set-cookie-parser: 2.7.1 + optionalDependencies: + react-dom: 19.1.1(react@19.1.1) + + react-style-singleton@2.2.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + get-nonce: 1.0.1 + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.9 + + react@19.1.1: {} + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + resolve-from@4.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.46.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.46.2 + '@rollup/rollup-android-arm64': 4.46.2 + '@rollup/rollup-darwin-arm64': 4.46.2 + '@rollup/rollup-darwin-x64': 4.46.2 + '@rollup/rollup-freebsd-arm64': 4.46.2 + '@rollup/rollup-freebsd-x64': 4.46.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 + '@rollup/rollup-linux-arm-musleabihf': 4.46.2 + '@rollup/rollup-linux-arm64-gnu': 4.46.2 + '@rollup/rollup-linux-arm64-musl': 4.46.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 + '@rollup/rollup-linux-ppc64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-musl': 4.46.2 + '@rollup/rollup-linux-s390x-gnu': 4.46.2 + '@rollup/rollup-linux-x64-gnu': 4.46.2 + '@rollup/rollup-linux-x64-musl': 4.46.2 + '@rollup/rollup-win32-arm64-msvc': 4.46.2 + '@rollup/rollup-win32-ia32-msvc': 4.46.2 + '@rollup/rollup-win32-x64-msvc': 4.46.2 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + scheduler@0.26.0: {} + + semver@6.3.1: {} + + semver@7.7.2: {} + + set-cookie-parser@2.7.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shallowequal@1.1.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + sockette@2.0.6: {} + + sonner@2.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + source-map-js@1.2.1: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-json-comments@3.1.1: {} + + style-mod@4.1.2: {} + + styled-components@6.1.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@emotion/is-prop-valid': 1.2.2 + '@emotion/unitless': 0.8.1 + '@types/stylis': 4.2.5 + css-to-react-native: 3.2.0 + csstype: 3.1.3 + postcss: 8.4.49 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + shallowequal: 1.1.0 + stylis: 4.3.2 + tslib: 2.6.2 + + stylis@4.3.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swr@2.3.4(react@19.1.1): + dependencies: + dequal: 2.0.3 + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + tabbable@6.2.0: {} + + tailwind-merge@3.3.1: {} + + tailwind-scrollbar@4.0.2(react@19.1.1)(tailwindcss@4.1.11): + dependencies: + prism-react-renderer: 2.4.1(react@19.1.1) + tailwindcss: 4.1.11 + transitivePeerDependencies: + - react + + tailwindcss@4.1.11: {} + + tapable@2.2.2: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + tiny-case@1.0.3: {} + + tiny-warning@1.0.3: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + toposort@2.0.2: {} + + ts-api-utils@2.1.0(typescript@5.9.2): + dependencies: + typescript: 5.9.2 + + ts-essentials@10.1.1(typescript@5.9.2): + optionalDependencies: + typescript: 5.9.2 + + ts-toolbelt@9.6.0: {} + + tslib@2.6.2: {} + + tslib@2.8.1: {} + + turbo-darwin-64@2.5.5: + optional: true + + turbo-darwin-arm64@2.5.5: + optional: true + + turbo-linux-64@2.5.5: + optional: true + + turbo-linux-arm64@2.5.5: + optional: true + + turbo-windows-64@2.5.5: + optional: true + + turbo-windows-arm64@2.5.5: + optional: true + + turbo@2.5.5: + optionalDependencies: + turbo-darwin-64: 2.5.5 + turbo-darwin-arm64: 2.5.5 + turbo-linux-64: 2.5.5 + turbo-linux-arm64: 2.5.5 + turbo-windows-64: 2.5.5 + turbo-windows-arm64: 2.5.5 + + tw-animate-css@1.3.6: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@2.19.0: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/parser': 8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.39.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.9.2) + eslint: 9.32.0(jiti@2.4.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + typescript@5.9.2: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@7.10.0: {} + + undici@6.21.3: {} + + unplugin@1.16.1: + dependencies: + acorn: 8.15.0 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.9 + + use-sidecar@1.1.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.9 + + use-sync-external-store@1.5.0(react@19.1.1): + dependencies: + react: 19.1.1 + + uuid@11.1.0: {} + + vite-plugin-full-reload@1.2.0: + dependencies: + picocolors: 1.1.1 + picomatch: 2.3.1 + + vite-plugin-manifest-sri@0.2.0: {} + + vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1): + dependencies: + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.46.2 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 24.2.0 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.30.1 + + w3c-keyname@2.2.8: {} + + webpack-virtual-modules@0.6.2: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yallist@5.0.0: {} + + yocto-queue@0.1.0: {} + + yup@1.7.0: + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 diff --git a/postcss.config.cjs b/postcss.config.cjs deleted file mode 100644 index 0cc56abe4..000000000 --- a/postcss.config.cjs +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - plugins: [ - require('postcss-import'), - // We want to make use of nesting following the CSS Nesting spec, and not the - // SASS style nesting. - // - // @see https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting - require('tailwindcss/nesting')(require('postcss-nesting')), - require('tailwindcss'), - require('autoprefixer'), - require('postcss-preset-env')({ - features: { - 'nesting-rules': false, - }, - }), - ], -}; diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 000000000..73b71ff17 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,10 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + 'postcss-import': {}, + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +}; + +export default config; diff --git a/public/favicons/apple-touch-icon.png b/public/favicons/apple-touch-icon.png new file mode 100644 index 000000000..e834788f8 Binary files /dev/null and b/public/favicons/apple-touch-icon.png differ diff --git a/public/favicons/favicon-180x180.png b/public/favicons/favicon-180x180.png deleted file mode 100644 index f9e792cf0..000000000 Binary files a/public/favicons/favicon-180x180.png and /dev/null differ diff --git a/public/favicons/favicon-96x96.png b/public/favicons/favicon-96x96.png new file mode 100644 index 000000000..57d0fe3e3 Binary files /dev/null and b/public/favicons/favicon-96x96.png differ diff --git a/public/favicons/favicon.ico b/public/favicons/favicon.ico new file mode 100644 index 000000000..946b0a0ea Binary files /dev/null and b/public/favicons/favicon.ico differ diff --git a/public/favicons/favicon.svg b/public/favicons/favicon.svg new file mode 100644 index 000000000..e87822ab3 --- /dev/null +++ b/public/favicons/favicon.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/public/favicons/site.webmanifest b/public/favicons/site.webmanifest new file mode 100644 index 000000000..e74c28b30 --- /dev/null +++ b/public/favicons/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Pyrodactyl", + "short_name": "Pyrodactyl", + "icons": [ + { + "src": "/favicons/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/favicons/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/public/favicons/web-app-manifest-192x192.png b/public/favicons/web-app-manifest-192x192.png new file mode 100644 index 000000000..a94de4942 Binary files /dev/null and b/public/favicons/web-app-manifest-192x192.png differ diff --git a/public/favicons/web-app-manifest-512x512.png b/public/favicons/web-app-manifest-512x512.png new file mode 100644 index 000000000..8393655d9 Binary files /dev/null and b/public/favicons/web-app-manifest-512x512.png differ diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 89f315cba..b94ff89cb 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -3,12 +3,22 @@ "language": "Language", "save_changes": "Save Changes", "loading": "Loading...", + "updating": "Updating...", "error": "Error", "creating": "Creating...", - "success": "Success" + "success": "Success", + "email": "Email", + "event": "Event", + "events": "Events", + "enter_value": "Enter value...", + "deleting": "Deleting...", + "not_set": "Not set", + "rules": "Rules", + "confirmation_modal": "ConfirmationModal" }, "settings": { "your_settings": "Your Settings", + "account_email": "Account Email", "account_info": "Account Information", "password_auth": "Password and Authentication", "app": "App", @@ -16,6 +26,9 @@ "email_address": "Email Address", "language": "Language", "multi_factor_auth": "Multi-Factor Authentication", + "version": "Version", + "commit": "Commit", + "filter": "Filter", "panel_version": "Panel Version", "panel_version_desc": "This is useful to provide Pyro staff if you run into an unexpected issue.", "two_factor_required": "2-Factor Required", @@ -74,22 +87,46 @@ }, "api": { "account_api": "Account API", - "create_api_key": "Create API Key", + "·": "Create API Key", + "key": "Key", "api_keys": "API Keys", "delete_api_key_title": "Delete API Key", + "create_key": "Create Key", + "create_api_key": "Create Api Key", "delete_key": "Delete Key", - "delete_api_key_desc": "All requests using the {{key}} key will be invalidated.", - "no_api_keys": "No API keys exist for this account.", + "all_using": "All requests using the", + "key_invalidated": "key will be invalidated", + "no_api_keys": "No API keys", "last_used": "Last used:", "never": "Never", "key_description": "Description", "key_description_description": "A description of this API key.", "allowed_ips": "Allowed IPs", - "allowed_ips_description": "Leave blank to allow any IP address to use this API key, otherwise provide each IP address on a new line." + "allowed_ips_description": "Leave blank to allow any IP address to use this API key, otherwise provide each IP address on a new line.", + "load_keys": "Loading your API keys...", + "no_keys_created": "You haven't created any API keys yet. Create one to get started with the API." }, "activity_log": { + "log": "Activity Log", "title": "Account Activity Log", - "clear_filters": "Clear Filters" + "clear_filters": "Clear All Filters", + "adjust_filters": "Adjust Filters", + "toggle_filters": "Toggle Filters (Ctrl+F)", + "events": "Activity Events", + "auto_refresh": "Auto Refresh (Ctrl+R)", + "export_csv": "Export CSV (Ctrl+E)", + "export": "Export", + "live": "Live", + "refresh": "Refresh", + "filters": "Filters", + "all_events": "All Events", + "all_time": "All Time", + "last_hour": "Last Hour", + "last_24": "Last 24 Hours", + "last_7": "Last 7 Days", + "last_30": "Last 30 Days", + "try_adjust": "Try adjusting your filters or search terms to find the activity you're looking for.", + "no_logs": "Activity logs will appear here as you use your account. Check back later or perform some actions to see them here." }, "ssh_key": { "name": "SSH Key Name", @@ -104,7 +141,114 @@ "delete_confirm": "Delete Key", "delete_message": "Removing the {name} SSH key will invalidate its usage across the Panel." }, + "home": { + "show_personal": "Show personal servers", + "show_others": "Show other servers", + "view_list": "View servers in a list layout.", + "view_grid": "View servers in a grid layout.", + "no_servers_found": "No servers found", + "no_others_found": "No other servers found", + "no_display_servers": "There are no other servers to display.", + "no_associated_servers": "There are no servers associated with your account.", + "servers": { + "sit_tight": "Sit tight!", + "cpu": "CPU", + "ram": "RAM", + "storage": "Storage" + } + }, "server": { + "shell": { + "current_software": "Current Software", + "software_management": "Software Management", + "no_software_selected": "No software selected", + "manage_server_software": "Manage your server's game or software configuration", + "error_in_software_click": "Error in change software click:", + "change_software": "Change Software", + "type_of_software": "Choose the type of game or software you want to run", + "software_version": "Choose the specific software version for your server", + "load_software": "Loading software options...", + "back_to_games": "Back to Games", + "software_config": "Software Configuration", + "custom_startup": "Enter custom startup command...", + "use_var_like": "Use variables like", + "docker_image": "Docker Image", + "runtime_env": "Container runtime environment for your server", + "env_variables": "Environment Variables", + "create_backup": "Create Backup", + "safety_options": "Safety Options", + "wipe_files": "Wipe Files", + "wipe_description": "Delete all files before installing new software", + "back_to_software": "Back to Software", + "review_changes": "Review Changes", + "no_software": "No software", + "startup_config": "Startup Configuration", + "startup_command": "Startup Command", + "variable_config": "Variable Configuration", + "subdomain_incompatible": "Subdomain Will Be Deleted", + "important_warning": "Important Warning", + "warning_1": "Your server will be stopped and reinstalled", + "warning_2": "This process may take several minutes to complete", + "warning_3": "Some files may be modified or removed during installation", + "warning_4": "Make sure you have backups of important data", + "back_to_overview": "Back to Overview", + "back_to_configure": "Back to Configure", + "apply_changes": "Apply Changes", + "loading_server_info": "Loading server information...", + "change_server_software_memo": "Change your server's game or software with our guided configuration wizard,", + "step_1": "overview", + "step_2": "select-game", + "step_3": "select-software", + "step_4": "configure", + "step_5": "review", + "no_backup_selected": "DANGER: No Backup Selected", + "wipe_no_backup": "You have chosen to wipe all files without creating a backup. This action will permanently delete ALL files on your server and cannot be undone.", + "last_warn_1": "All server files will be permanently deleted", + "last_warn_2": "Your server will be stopped and reinstalled", + "last_warn_3": "Any custom configurations or data will be lost", + "last_warn_4": "This action cannot be reversed", + "last_chance": "Are you absolutely sure you want to proceed without a backup?", + "of_four": "of 4", + "select_category": "Select Category" + }, + "users": { + "new_user": "New User", + "description": "Manage user access to your server. Grant specific permissions to other users to help you manage and maintain your server.", + "users": "users", + "no_additional_users": "Your server does not have any additional users. Add others to help you manage your server.", + "no_users_found": "No users found", + "zero_users": "0 users" + }, + "network": { + "subdomain": { + "edit": "Edit Subdomain", + "required": "A subdomain name is required.", + "min_name": "Subdomain must be at least 1 character.", + "max_name": "Subdomain cannot exceed 63 characters.", + "invalid": "Subdomain can only contain lowercase letters, numbers, and hyphens. It must start and end with a letter or number.", + "domain_required": "A domain must be selected.", + "failed_ability_check": "Failed to check availability. Please try again.", + "final_delete_check": "Are you sure you want to delete this subdomain? This will remove all associated DNS records and cannot be undone.", + "load_subdomain_config": "Loading subdomain configuration...", + "no_domains_configured": "No domains configured", + "subdomain_management": "Subdomain Management", + "contact_admin": "Contact your administrator to configure subdomain support for this server.", + "current_subdomain": "Current Subdomain", + "delete_subdomain": "Delete Subdomain", + "check_availability": "Checking availability", + "description": "Choose a unique name for your subdomain. Only lowercase letters, numbers, and hyphens are allowed.", + "confirm_delete": "Are you sure you want to delete this allocation?" + }, + "allocation": { + "notes": "Notes", + "notes_placeholder": "Add notes for this allocation...", + "add_notes": "Click to add notes...", + "already_primary": "This is already the primary allocation", + "make_primary": "Make this the primary allocation", + "connot_delete_primary": "Cannot delete the primary allocation", + "delete_allocation": "Delete this allocation" + } + }, "settings": { "header": "Settings", "debug_information": "Debug Information", @@ -113,6 +257,7 @@ "sftp_details": "SFTP Details", "server_address": "Server Address", "username": "Username", + "configure_settings": "Configure your server settings, manage SFTP access, and access debug information. Make changes to server name and reinstall when needed.", "sftp_password_note": "Your SFTP password is the same as the password you use to access this panel.", "launch_sftp": "Launch SFTP", "rename": { @@ -137,6 +282,11 @@ "reinstall_started": "Your server has begun the reinstallation process." } } + }, + "schedules": { + "no_schedules_found": "No schedules found", + "no_scheduled_tasks": "Your server does not have any scheduled tasks. Create one to automate server management.", + "automate_tasks": "Automate server tasks with scheduled commands. Create recurring tasks to manage your server, run backups, or execute custom commands." } }, "auth": { @@ -151,6 +301,7 @@ "return_to_login": "Return to Login", "email": "Email", "send_email": "Send Email", + "email_sent": "Email Sent", "reset_email_info": "We'll send you an email with a link to reset your password.", "two_factor_authentication": "Two Factor Authentication", "check_device_for_code": "Check device linked with your account for code.", @@ -167,7 +318,16 @@ "password_min_length": "Your new password should be at least 8 characters in length.", "password_confirmation_required": "Your new password does not match.", "password_confirmation_mismatch": "Your new password does not match.", - "email_required_reset": "Please enter your email address to reset your password." + "email_required_reset": "Please enter your email address to reset your password.", + "invalid_username_password": "Invalid username or password. Please try again." + + }, + "catpcha": { + "captcha_error": "Captcha Error", + "verification_failed": "Captcha verification failed. Please try again.", + "please_complete": "Please complete the captcha verification", + "no_response": "Captcha enabled but no response available" + } }, "errors": { @@ -203,5 +363,14 @@ "build": "Build", "commit": "Commit", "close": "Close", - "error": "Error" + "error": "Error", + "read_only": "Read-only", + "required": "Required", + "optional": "Optional", + "to": "to", + "category": "Category", + "yes": "Yes", + "no": "No", + "warning": "Warning", + "delete": "delete" } diff --git a/public/themes/pterodactyl/css/terminal.css b/public/themes/pterodactyl/css/terminal.css index 3c46a8750..b93e96ba4 100644 --- a/public/themes/pterodactyl/css/terminal.css +++ b/public/themes/pterodactyl/css/terminal.css @@ -1,5 +1,5 @@ /*Design for Terminal*/ -@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro'); +@import url('https://fonts.bunny.net/css?family=Source+Code+Pro'); #terminal-body { background: rgb(26, 26, 26); diff --git a/resources/css/app.css b/resources/css/app.css index b5c61c956..e69de29bb 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/resources/lang/en/strings.php b/resources/lang/en/strings.php index ddade2cea..87d613843 100644 --- a/resources/lang/en/strings.php +++ b/resources/lang/en/strings.php @@ -73,7 +73,6 @@ return [ 'owner' => 'Owner', 'admin' => 'Admin', 'subuser' => 'Subuser', - 'captcha_invalid' => 'The provided captcha is invalid.', 'tasks' => 'Tasks', 'seconds' => 'Seconds', 'minutes' => 'Minutes', diff --git a/resources/scripts/api/auth/login.ts b/resources/scripts/api/auth/login.ts index 57552e5ed..1358c74b6 100644 --- a/resources/scripts/api/auth/login.ts +++ b/resources/scripts/api/auth/login.ts @@ -1,38 +1,61 @@ import http from '@/api/http'; -export interface LoginResponse { +interface LoginData { + user: string; + password: string; + [key: string]: any; // Allow additional fields like captcha responses +} + +interface LoginResponse { complete: boolean; intended?: string; confirmationToken?: string; + error?: string; } -export interface LoginData { - username: string; - password: string; - recaptchaData?: string | null; -} +export default async (data: LoginData): Promise => { + try { + await http.get('/sanctum/csrf-cookie'); -export default ({ username, password, recaptchaData }: LoginData): Promise => { - return new Promise((resolve, reject) => { - http.get('/sanctum/csrf-cookie') - .then(() => - http.post('/auth/login', { - user: username, - password, - 'g-recaptcha-response': recaptchaData, - }), - ) - .then((response) => { - if (!(response.data instanceof Object)) { - return reject(new Error('An error occurred while processing the login request.')); - } + // Pass through all data including captcha responses + const payload: Record = { + ...data, + }; - return resolve({ - complete: response.data.data.complete, - intended: response.data.data.intended || undefined, - confirmationToken: response.data.data.confirmation_token || undefined, - }); - }) - .catch(reject); - }); + const response = await http.post('/auth/login', payload); + + if (!response.data || typeof response.data !== 'object') { + throw new Error('Invalid server response format'); + } + + return { + complete: response.data.complete ?? response.data.data?.complete ?? false, + intended: response.data.intended ?? response.data.data?.intended, + confirmationToken: + response.data.confirmationToken ?? + response.data.data?.confirmation_token ?? + response.data.data?.confirmationToken, + error: response.data.error ?? response.data.message, + }; + } catch (error: any) { + const loginError = new Error( + error.response?.data?.error ?? + error.response?.data?.message ?? + error.message ?? + 'Login failed. Please try again.', + ) as any; + + loginError.response = error.response; + loginError.detail = error.response?.data?.errors?.[0]?.detail; + loginError.code = error.response?.data?.errors?.[0]?.code; + + console.error('Login API Error:', { + status: error.response?.status, + data: error.response?.data, + detail: loginError.detail, + message: loginError.message, + }); + + throw loginError; + } }; diff --git a/resources/scripts/api/auth/performPasswordReset.ts b/resources/scripts/api/auth/performPasswordReset.ts index 0106e8d26..f1b7b8c46 100644 --- a/resources/scripts/api/auth/performPasswordReset.ts +++ b/resources/scripts/api/auth/performPasswordReset.ts @@ -4,6 +4,7 @@ interface Data { token: string; password: string; passwordConfirmation: string; + [key: string]: string; } interface PasswordResetResponse { @@ -15,9 +16,7 @@ export default (email: string, data: Data): Promise => { return new Promise((resolve, reject) => { http.post('/auth/password/reset', { email, - token: data.token, - password: data.password, - password_confirmation: data.passwordConfirmation, + ...data, }) .then((response) => resolve({ diff --git a/resources/scripts/api/auth/requestPasswordResetEmail.ts b/resources/scripts/api/auth/requestPasswordResetEmail.ts deleted file mode 100644 index d68fa4447..000000000 --- a/resources/scripts/api/auth/requestPasswordResetEmail.ts +++ /dev/null @@ -1,9 +0,0 @@ -import http from '@/api/http'; - -export default (email: string, recaptchaData?: string): Promise => { - return new Promise((resolve, reject) => { - http.post('/auth/password', { email, 'g-recaptcha-response': recaptchaData }) - .then((response) => resolve(response.data.status || '')) - .catch(reject); - }); -}; diff --git a/resources/scripts/api/definitions/index.d.ts b/resources/scripts/api/definitions/index.d.ts index 3f6a6750d..92eef5a6d 100644 --- a/resources/scripts/api/definitions/index.d.ts +++ b/resources/scripts/api/definitions/index.d.ts @@ -4,7 +4,7 @@ import { FractalResponseData, FractalResponseList } from '../http'; export type UUID = string; -export interface Model {} +export type Model = object; interface ModelWithRelationships extends Model { relationships: Record; diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index f692909f4..9b38c01d4 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -50,7 +50,7 @@ export function httpErrorToHuman(error: any): string { if (typeof data === 'string') { try { data = JSON.parse(data); - } catch (e) { + } catch { // do nothing, bad json } } diff --git a/resources/scripts/api/mclo.gs/mclogsApi.ts b/resources/scripts/api/mclo.gs/mclogsApi.ts new file mode 100644 index 000000000..436dd2095 --- /dev/null +++ b/resources/scripts/api/mclo.gs/mclogsApi.ts @@ -0,0 +1,95 @@ +export interface MclogsInsight { + id: string; + name: string; + type: string; + version: string; + title: string; + analysis: { + problems: Array<{ + message: string; + counter: number; + entry: { + level: number; + time: string | null; + prefix: string; + lines: Array<{ + number: number; + content: string; + }>; + }; + solutions: Array<{ + message: string; + }>; + }>; + information: Array<{ + message: string; + counter: number; + label: string; + value: string; + entry: { + level: number; + time: string | null; + prefix: string; + lines: Array<{ + number: number; + content: string; + }>; + }; + }>; + }; +} + +export interface MclogsAnalyzeRequest { + content: string; +} + +export interface MclogsErrorResponse { + success: false; + error: string; +} + +/** + * Analyzes log content using the mclo.gs API without saving it + */ +export const analyzeLogs = async (logContent: string): Promise => { + const response = await fetch('https://api.mclo.gs/1/analyse', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + content: logContent, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if ('success' in data && data.success === false) { + throw new Error(data.error || 'Failed to analyze logs'); + } + + return data as MclogsInsight; +}; + +/** + * Gets insights from an existing mclo.gs paste + */ +export const getInsights = async (logId: string): Promise => { + const response = await fetch(`https://api.mclo.gs/1/insights/${logId}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if ('success' in data && data.success === false) { + throw new Error(data.error || 'Log not found'); + } + + return data as MclogsInsight; +}; diff --git a/resources/scripts/api/server/applyEggChange.ts b/resources/scripts/api/server/applyEggChange.ts new file mode 100644 index 000000000..79bcecc20 --- /dev/null +++ b/resources/scripts/api/server/applyEggChange.ts @@ -0,0 +1,26 @@ +import http from '@/api/http'; + +export interface ApplyEggChangeRequest { + egg_id: number; + nest_id: number; + docker_image?: string; + startup_command?: string; + environment?: Record; + should_backup?: boolean; + should_wipe?: boolean; +} + +export interface ApplyEggChangeResponse { + message: string; + operation_id: string; + status: string; +} + +/** + * Apply egg configuration changes to a server asynchronously. + * This initiates a background operation to change the server's egg configuration. + */ +export default async (uuid: string, data: ApplyEggChangeRequest): Promise => { + const { data: response } = await http.post(`/api/client/servers/${uuid}/settings/egg/apply`, data); + return response; +}; diff --git a/resources/scripts/api/server/backups/deleteBackup.ts b/resources/scripts/api/server/backups/deleteBackup.ts deleted file mode 100644 index 01f48d23f..000000000 --- a/resources/scripts/api/server/backups/deleteBackup.ts +++ /dev/null @@ -1,9 +0,0 @@ -import http from '@/api/http'; - -export default (uuid: string, backup: string): Promise => { - return new Promise((resolve, reject) => { - http.delete(`/api/client/servers/${uuid}/backups/${backup}`) - .then(() => resolve()) - .catch(reject); - }); -}; diff --git a/resources/scripts/api/server/backups/deleteServerBackup.ts b/resources/scripts/api/server/backups/deleteServerBackup.ts new file mode 100644 index 000000000..1d9d0b0ca --- /dev/null +++ b/resources/scripts/api/server/backups/deleteServerBackup.ts @@ -0,0 +1,5 @@ +import http from '@/api/http'; + +export default async (uuid: string, backup: string): Promise => { + await http.delete(`/api/client/servers/${uuid}/backups/${backup}`); +}; diff --git a/resources/scripts/api/server/backups/getBackupDownloadUrl.ts b/resources/scripts/api/server/backups/getBackupDownloadUrl.ts deleted file mode 100644 index 70a3ae5e4..000000000 --- a/resources/scripts/api/server/backups/getBackupDownloadUrl.ts +++ /dev/null @@ -1,9 +0,0 @@ -import http from '@/api/http'; - -export default (uuid: string, backup: string): Promise => { - return new Promise((resolve, reject) => { - http.get(`/api/client/servers/${uuid}/backups/${backup}/download`) - .then(({ data }) => resolve(data.attributes.url)) - .catch(reject); - }); -}; diff --git a/resources/scripts/api/server/backups/getServerBackupDownloadUrl.ts b/resources/scripts/api/server/backups/getServerBackupDownloadUrl.ts new file mode 100644 index 000000000..263f16347 --- /dev/null +++ b/resources/scripts/api/server/backups/getServerBackupDownloadUrl.ts @@ -0,0 +1,6 @@ +import http from '@/api/http'; + +export default async (uuid: string, backup: string): Promise => { + const { data } = await http.get(`/api/client/servers/${uuid}/backups/${backup}/download`); + return data.attributes.url; +}; diff --git a/resources/scripts/api/server/backups/index.ts b/resources/scripts/api/server/backups/index.ts index 4f1311fdf..9e84a1f61 100644 --- a/resources/scripts/api/server/backups/index.ts +++ b/resources/scripts/api/server/backups/index.ts @@ -1,7 +1,10 @@ import http from '@/api/http'; -export const restoreServerBackup = async (uuid: string, backup: string, truncate?: boolean): Promise => { - await http.post(`/api/client/servers/${uuid}/backups/${backup}/restore`, { - truncate, - }); +export const restoreServerBackup = async (uuid: string, backup: string): Promise => { + await http.post(`/api/client/servers/${uuid}/backups/${backup}/restore`, {}); }; + +export { default as createServerBackup } from './createServerBackup'; +export { default as deleteServerBackup } from './deleteServerBackup'; +export { default as getServerBackupDownloadUrl } from './getServerBackupDownloadUrl'; +export { default as renameServerBackup } from './renameServerBackup'; diff --git a/resources/scripts/api/server/backups/renameServerBackup.ts b/resources/scripts/api/server/backups/renameServerBackup.ts new file mode 100644 index 000000000..2e6c7a958 --- /dev/null +++ b/resources/scripts/api/server/backups/renameServerBackup.ts @@ -0,0 +1,11 @@ +import http from '@/api/http'; +import { ServerBackup } from '@/api/server/types'; +import { rawDataToServerBackup } from '@/api/transformers'; + +export default async (uuid: string, backup: string, name: string): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/backups/${backup}/rename`, { + name: name, + }); + + return rawDataToServerBackup(data); +}; diff --git a/resources/scripts/api/server/network/subdomain.ts b/resources/scripts/api/server/network/subdomain.ts new file mode 100644 index 000000000..6733d1e1f --- /dev/null +++ b/resources/scripts/api/server/network/subdomain.ts @@ -0,0 +1,64 @@ +import http from '@/api/http'; + +export interface SubdomainInfo { + supported: boolean; + current_subdomain?: { + object: string; + attributes: { + subdomain: string; + domain: string; + domain_id: number; + full_domain: string; + is_active: boolean; + }; + }; + available_domains: Array<{ + id: number; + name: string; + is_active: boolean; + }>; + message?: string; +} + +export interface AvailabilityResponse { + available: boolean; + message: string; +} + +export const getSubdomainInfo = (uuid: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/subdomain`) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; + +export const setSubdomain = (uuid: string, subdomain: string, domainId: number): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/subdomain`, { + subdomain, + domain_id: domainId, + }) + .then(() => resolve()) + .catch(reject); + }); +}; + +export const deleteSubdomain = (uuid: string): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/subdomain`) + .then(() => resolve()) + .catch(reject); + }); +}; + +export const checkSubdomainAvailability = (uuid: string, subdomain: string, domainId: number): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/subdomain/check-availability`, { + subdomain, + domain_id: domainId, + }) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; \ No newline at end of file diff --git a/resources/scripts/api/server/previewEggChange.ts b/resources/scripts/api/server/previewEggChange.ts new file mode 100644 index 000000000..725ffcb18 --- /dev/null +++ b/resources/scripts/api/server/previewEggChange.ts @@ -0,0 +1,40 @@ +import http from '@/api/http'; + +export interface EggPreview { + egg: { + id: number; + name: string; + description: string; + startup: string; + }; + variables: Array<{ + id: number; + name: string; + description: string; + env_variable: string; + default_value: string; + user_viewable: boolean; + user_editable: boolean; + rules: string; + }>; + docker_images: Record; + default_docker_image: string | null; + warnings?: Array<{ + type: string; + message: string; + severity: string; + }>; +} + +/** + * Preview egg configuration changes before applying them. + * Returns egg details, variables, and available Docker images. + */ +export default async (uuid: string, eggId: number, nestId: number): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/settings/egg/preview`, { + egg_id: eggId, + nest_id: nestId, + }); + + return data; +}; diff --git a/resources/scripts/api/server/processStartupCommand.ts b/resources/scripts/api/server/processStartupCommand.ts new file mode 100644 index 000000000..78412e09f --- /dev/null +++ b/resources/scripts/api/server/processStartupCommand.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, command: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/startup/command/process`, { command }) + .then(({ data }) => resolve(data.processed_command)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/resetStartupCommand.ts b/resources/scripts/api/server/resetStartupCommand.ts new file mode 100644 index 000000000..1d684d05e --- /dev/null +++ b/resources/scripts/api/server/resetStartupCommand.ts @@ -0,0 +1,7 @@ +import http from '@/api/http'; + +export default async (uuid: string): Promise => { + const { data } = await http.get(`/api/client/servers/${uuid}/startup/command/default`); + + return data.default_startup_command; +}; diff --git a/resources/scripts/api/server/revertDockerImage.ts b/resources/scripts/api/server/revertDockerImage.ts new file mode 100644 index 000000000..a1b692089 --- /dev/null +++ b/resources/scripts/api/server/revertDockerImage.ts @@ -0,0 +1,5 @@ +import http from '@/api/http'; + +export default async (uuid: string): Promise => { + await http.post(`/api/client/servers/${uuid}/settings/docker-image/revert`, { confirm: true }); +}; diff --git a/resources/scripts/api/server/serverOperations.ts b/resources/scripts/api/server/serverOperations.ts new file mode 100644 index 000000000..30b1e2279 --- /dev/null +++ b/resources/scripts/api/server/serverOperations.ts @@ -0,0 +1,181 @@ +import React from 'react'; + +import http from '@/api/http'; + +/** + * Server operation status constants. + */ +export const OPERATION_STATUS = { + PENDING: 'pending', + RUNNING: 'running', + COMPLETED: 'completed', + FAILED: 'failed', + CANCELLED: 'cancelled', +} as const; + +export type OperationStatus = (typeof OPERATION_STATUS)[keyof typeof OPERATION_STATUS]; + +/** + * Polling configuration for operation status updates. + */ +export const POLLING_CONFIG = { + INITIAL_INTERVAL: 2000, + MAX_INTERVAL: 8000, + MAX_ATTEMPTS: 90, + JITTER_RANGE: 500, + BACKOFF_MULTIPLIER: 1.05, + BACKOFF_THRESHOLD: 5, +}; + +export interface ServerOperation { + operation_id: string; + type: string; + status: OperationStatus; + message: string; + created_at: string; + updated_at: string; + parameters?: Record; + is_active: boolean; + is_completed: boolean; + has_failed: boolean; +} + +export interface ApplyEggChangeAsyncResponse { + message: string; + operation_id: string; + status: string; +} + +/** + * Get specific operation status by ID. + */ +export const getOperationStatus = async (uuid: string, operationId: string): Promise => { + const { data } = await http.get(`/api/client/servers/${uuid}/operations/${operationId}`); + return data; +}; + +/** + * Get all operations for a server. + */ +export const getServerOperations = async (uuid: string): Promise<{ operations: ServerOperation[] }> => { + const { data } = await http.get(`/api/client/servers/${uuid}/operations`); + return data; +}; + +/** + * Poll operation status with exponential backoff and jitter. + */ +export const pollOperationStatus = ( + uuid: string, + operationId: string, + onUpdate: (operation: ServerOperation) => void, + onComplete: (operation: ServerOperation) => void, + onError: (error: Error) => void, +): (() => void) => { + let timeoutId: NodeJS.Timeout | null = null; + let intervalMs = POLLING_CONFIG.INITIAL_INTERVAL; + const maxInterval = POLLING_CONFIG.MAX_INTERVAL; + let attempts = 0; + let stopped = false; + + const poll = async () => { + if (stopped) return; + + try { + attempts++; + + if (attempts > POLLING_CONFIG.MAX_ATTEMPTS) { + onError(new Error('Operation polling timed out after 15 minutes')); + return; + } + + const operation = await getOperationStatus(uuid, operationId); + + if (stopped) return; + + onUpdate(operation); + + if (operation.is_completed || operation.has_failed) { + onComplete(operation); + return; + } + + if (operation.is_active) { + if (attempts > POLLING_CONFIG.BACKOFF_THRESHOLD) { + intervalMs = Math.min(intervalMs * POLLING_CONFIG.BACKOFF_MULTIPLIER, maxInterval); + } + + const jitter = Math.random() * POLLING_CONFIG.JITTER_RANGE; + timeoutId = setTimeout(poll, intervalMs + jitter); + } else { + onError(new Error('Operation is no longer active')); + } + } catch (error) { + if (!stopped) { + onError(error as Error); + } + } + }; + + timeoutId = setTimeout(poll, 1000); + + return () => { + stopped = true; + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + }; +}; + +/** + * React hook for managing operation polling lifecycle. + */ +export const useOperationPolling = () => { + const activePollers = React.useRef(new Map void>()).current; + + React.useEffect(() => { + return () => { + activePollers.forEach((cleanup) => cleanup()); + activePollers.clear(); + }; + }, [activePollers]); + + const startPolling = React.useCallback( + ( + uuid: string, + operationId: string, + onUpdate: (operation: ServerOperation) => void, + onComplete: (operation: ServerOperation) => void, + onError: (error: Error) => void, + ) => { + stopPolling(operationId); + const cleanup = pollOperationStatus(uuid, operationId, onUpdate, onComplete, onError); + activePollers.set(operationId, cleanup); + }, + [activePollers], + ); + + const stopPolling = React.useCallback( + (operationId: string) => { + const cleanup = activePollers.get(operationId); + if (cleanup) { + cleanup(); + activePollers.delete(operationId); + } + }, + [activePollers], + ); + + const stopAllPolling = React.useCallback(() => { + activePollers.forEach((cleanup) => cleanup()); + activePollers.clear(); + }, [activePollers]); + + return { + startPolling, + stopPolling, + stopAllPolling, + hasActivePolling: (operationId: string) => activePollers.has(operationId), + }; +}; diff --git a/resources/scripts/api/server/setSelectedEggImage.ts b/resources/scripts/api/server/setSelectedEggImage.ts deleted file mode 100644 index 0c23a0d00..000000000 --- a/resources/scripts/api/server/setSelectedEggImage.ts +++ /dev/null @@ -1,11 +0,0 @@ -import http from '@/api/http'; - -export default async (uuid: string, eggid: number, nestid: number): Promise => { - await http.put(`/api/client/servers/${uuid}/settings/egg`, { egg_id: eggid, nest_id: nestid }); - - const { data } = await http.get(`/api/client/servers/${uuid}/startup`); - const docker_images = data.meta.docker_images || {}; - const image = Object.values(docker_images)[0] as string; - - await http.put(`/api/client/servers/${uuid}/settings/docker-image`, { docker_image: image }); -}; diff --git a/resources/scripts/api/server/updateStartupCommand.ts b/resources/scripts/api/server/updateStartupCommand.ts new file mode 100644 index 000000000..06c9c1070 --- /dev/null +++ b/resources/scripts/api/server/updateStartupCommand.ts @@ -0,0 +1,7 @@ +import http from '@/api/http'; + +export default async (uuid: string, startup: string): Promise => { + const { data } = await http.put(`/api/client/servers/${uuid}/startup/command`, { startup }); + + return data.meta.startup_command; +}; diff --git a/resources/scripts/api/swr/getServerStartup.ts b/resources/scripts/api/swr/getServerStartup.ts index df05bdb3f..069bd6c1b 100644 --- a/resources/scripts/api/swr/getServerStartup.ts +++ b/resources/scripts/api/swr/getServerStartup.ts @@ -10,6 +10,7 @@ interface Response { invocation: string; variables: ServerEggVariable[]; dockerImages: Record; + rawStartupCommand: string; } export default (uuid: string, fallbackData?: Response, config?: SWRConfiguration) => @@ -24,6 +25,7 @@ export default (uuid: string, fallbackData?: Response, config?: SWRConfiguration variables, invocation: data.meta.startup_command, dockerImages: data.meta.docker_images || {}, + rawStartupCommand: data.meta.raw_startup_command, }; }, { fallbackData, errorRetryCount: 3, ...(config ?? {}) }, diff --git a/resources/scripts/assets/css/GlobalStylesheet.ts b/resources/scripts/assets/css/GlobalStylesheet.ts index 86675c0da..ffc9f3d9a 100644 --- a/resources/scripts/assets/css/GlobalStylesheet.ts +++ b/resources/scripts/assets/css/GlobalStylesheet.ts @@ -1,9 +1,9 @@ import { createGlobalStyle } from 'styled-components'; export default createGlobalStyle` - * { - min-width: 0 - } + // * { + // min-width: 0 + // } html, body, #app { position: relative; diff --git a/resources/scripts/assets/tailwind.css b/resources/scripts/assets/tailwind.css index b5c61c956..d4973e8f6 100644 --- a/resources/scripts/assets/tailwind.css +++ b/resources/scripts/assets/tailwind.css @@ -1,3 +1,300 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import 'tailwindcss'; +@import 'tw-animate-css'; +@plugin 'tailwind-scrollbar'; + +@theme { + --font-jakarta: 'Plus Jakarta Sans', sans-serif; + + --color-inherit: inherit; + --color-current: currentColor; + --color-transparent: transparent; + --color-black: #000000; + --color-white: #fff; + + --color-slate-50: #f8fafc; + --color-slate-100: #f1f5f9; + --color-slate-200: #e2e8f0; + --color-slate-300: #cbd5e1; + --color-slate-400: #94a3b8; + --color-slate-500: #64748b; + --color-slate-600: #475569; + --color-slate-700: #334155; + --color-slate-800: #1e293b; + --color-slate-900: #0f172a; + --color-slate-950: #020617; + + --color-gray-50: #f9fafb; + --color-gray-100: #f3f4f6; + --color-gray-200: #e5e7eb; + --color-gray-300: #d1d5db; + --color-gray-400: #9ca3af; + --color-gray-500: #6b7280; + --color-gray-600: #4b5563; + --color-gray-700: #374151; + --color-gray-800: #1f2937; + --color-gray-900: #111827; + --color-gray-950: #030712; + + --color-zinc-50: #fafafa; + --color-zinc-100: #f4f4f5; + --color-zinc-200: #e4e4e7; + --color-zinc-300: #d4d4d8; + --color-zinc-400: #a1a1aa; + --color-zinc-500: #71717a; + --color-zinc-600: #52525b; + --color-zinc-700: #3f3f46; + --color-zinc-800: #27272a; + --color-zinc-900: #18181b; + --color-zinc-950: #09090b; + + --color-neutral-50: #fafafa; + --color-neutral-100: #f5f5f5; + --color-neutral-200: #e5e5e5; + --color-neutral-300: #d4d4d4; + --color-neutral-400: #a3a3a3; + --color-neutral-500: #737373; + --color-neutral-600: #525252; + --color-neutral-700: #404040; + --color-neutral-800: #262626; + --color-neutral-900: #171717; + --color-neutral-950: #0a0a0a; + + --color-stone-50: #fafaf9; + --color-stone-100: #f5f5f4; + --color-stone-200: #e7e5e4; + --color-stone-300: #d6d3d1; + --color-stone-400: #a8a29e; + --color-stone-500: #78716c; + --color-stone-600: #57534e; + --color-stone-700: #44403c; + --color-stone-800: #292524; + --color-stone-900: #1c1917; + --color-stone-950: #0c0a09; + + --color-red-50: #fef2f2; + --color-red-100: #fee2e2; + --color-red-200: #fecaca; + --color-red-300: #fca5a5; + --color-red-400: #f87171; + --color-red-500: #ef4444; + --color-red-600: #dc2626; + --color-red-700: #b91c1c; + --color-red-800: #991b1b; + --color-red-900: #7f1d1d; + --color-red-950: #450a0a; + + --color-orange-50: #fff7ed; + --color-orange-100: #ffedd5; + --color-orange-200: #fed7aa; + --color-orange-300: #fdba74; + --color-orange-400: #fb923c; + --color-orange-500: #f97316; + --color-orange-600: #ea580c; + --color-orange-700: #c2410c; + --color-orange-800: #9a3412; + --color-orange-900: #7c2d12; + --color-orange-950: #431407; + + --color-amber-50: #fffbeb; + --color-amber-100: #fef3c7; + --color-amber-200: #fde68a; + --color-amber-300: #fcd34d; + --color-amber-400: #fbbf24; + --color-amber-500: #f59e0b; + --color-amber-600: #d97706; + --color-amber-700: #b45309; + --color-amber-800: #92400e; + --color-amber-900: #78350f; + --color-amber-950: #451a03; + + --color-yellow-50: #fefce8; + --color-yellow-100: #fef9c3; + --color-yellow-200: #fef08a; + --color-yellow-300: #fde047; + --color-yellow-400: #facc15; + --color-yellow-500: #eab308; + --color-yellow-600: #ca8a04; + --color-yellow-700: #a16207; + --color-yellow-800: #854d0e; + --color-yellow-900: #713f12; + --color-yellow-950: #422006; + + --color-lime-50: #f7fee7; + --color-lime-100: #ecfccb; + --color-lime-200: #d9f99d; + --color-lime-300: #bef264; + --color-lime-400: #a3e635; + --color-lime-500: #84cc16; + --color-lime-600: #65a30d; + --color-lime-700: #4d7c0f; + --color-lime-800: #3f6212; + --color-lime-900: #365314; + --color-lime-950: #1a2e05; + + --color-green-50: #f0fdf4; + --color-green-100: #dcfce7; + --color-green-200: #bbf7d0; + --color-green-300: #86efac; + --color-green-400: #4ade80; + --color-green-500: #22c55e; + --color-green-600: #16a34a; + --color-green-700: #15803d; + --color-green-800: #166534; + --color-green-900: #14532d; + --color-green-950: #052e16; + + --color-emerald-50: #ecfdf5; + --color-emerald-100: #d1fae5; + --color-emerald-200: #a7f3d0; + --color-emerald-300: #6ee7b7; + --color-emerald-400: #34d399; + --color-emerald-500: #10b981; + --color-emerald-600: #059669; + --color-emerald-700: #047857; + --color-emerald-800: #065f46; + --color-emerald-900: #064e3b; + --color-emerald-950: #022c22; + + --color-teal-50: #f0fdfa; + --color-teal-100: #ccfbf1; + --color-teal-200: #99f6e4; + --color-teal-300: #5eead4; + --color-teal-400: #2dd4bf; + --color-teal-500: #14b8a6; + --color-teal-600: #0d9488; + --color-teal-700: #0f766e; + --color-teal-800: #115e59; + --color-teal-900: #134e4a; + --color-teal-950: #042f2e; + + --color-cyan-50: #ecfeff; + --color-cyan-100: #cffafe; + --color-cyan-200: #a5f3fc; + --color-cyan-300: #67e8f9; + --color-cyan-400: #22d3ee; + --color-cyan-500: #06b6d4; + --color-cyan-600: #0891b2; + --color-cyan-700: #0e7490; + --color-cyan-800: #155e75; + --color-cyan-900: #164e63; + --color-cyan-950: #083344; + + --color-sky-50: #f0f9ff; + --color-sky-100: #e0f2fe; + --color-sky-200: #bae6fd; + --color-sky-300: #7dd3fc; + --color-sky-400: #38bdf8; + --color-sky-500: #0ea5e9; + --color-sky-600: #0284c7; + --color-sky-700: #0369a1; + --color-sky-800: #075985; + --color-sky-900: #0c4a6e; + --color-sky-950: #082f49; + + --color-blue-50: #eff6ff; + --color-blue-100: #dbeafe; + --color-blue-200: #bfdbfe; + --color-blue-300: #93c5fd; + --color-blue-400: #60a5fa; + --color-blue-500: #3b82f6; + --color-blue-600: #2563eb; + --color-blue-700: #1d4ed8; + --color-blue-800: #1e40af; + --color-blue-900: #1e3a8a; + --color-blue-950: #172554; + + --color-indigo-50: #eef2ff; + --color-indigo-100: #e0e7ff; + --color-indigo-200: #c7d2fe; + --color-indigo-300: #a5b4fc; + --color-indigo-400: #818cf8; + --color-indigo-500: #6366f1; + --color-indigo-600: #4f46e5; + --color-indigo-700: #4338ca; + --color-indigo-800: #3730a3; + --color-indigo-900: #312e81; + --color-indigo-950: #1e1b4b; + + --color-violet-50: #f5f3ff; + --color-violet-100: #ede9fe; + --color-violet-200: #ddd6fe; + --color-violet-300: #c4b5fd; + --color-violet-400: #a78bfa; + --color-violet-500: #8b5cf6; + --color-violet-600: #7c3aed; + --color-violet-700: #6d28d9; + --color-violet-800: #5b21b6; + --color-violet-900: #4c1d95; + --color-violet-950: #2e1065; + + --color-purple-50: #faf5ff; + --color-purple-100: #f3e8ff; + --color-purple-200: #e9d5ff; + --color-purple-300: #d8b4fe; + --color-purple-400: #c084fc; + --color-purple-500: #a855f7; + --color-purple-600: #9333ea; + --color-purple-700: #7e22ce; + --color-purple-800: #6b21a8; + --color-purple-900: #581c87; + --color-purple-950: #3b0764; + + --color-fuchsia-50: #fdf4ff; + --color-fuchsia-100: #fae8ff; + --color-fuchsia-200: #f5d0fe; + --color-fuchsia-300: #f0abfc; + --color-fuchsia-400: #e879f9; + --color-fuchsia-500: #d946ef; + --color-fuchsia-600: #c026d3; + --color-fuchsia-700: #a21caf; + --color-fuchsia-800: #86198f; + --color-fuchsia-900: #701a75; + --color-fuchsia-950: #4a044e; + + --color-pink-50: #fdf2f8; + --color-pink-100: #fce7f3; + --color-pink-200: #fbcfe8; + --color-pink-300: #f9a8d4; + --color-pink-400: #f472b6; + --color-pink-500: #ec4899; + --color-pink-600: #db2777; + --color-pink-700: #be185d; + --color-pink-800: #9d174d; + --color-pink-900: #831843; + --color-pink-950: #500724; + + --color-rose-50: #fff1f2; + --color-rose-100: #ffe4e6; + --color-rose-200: #fecdd3; + --color-rose-300: #fda4af; + --color-rose-400: #fb7185; + --color-rose-500: #f43f5e; + --color-rose-600: #e11d48; + --color-rose-700: #be123c; + --color-rose-800: #9f1239; + --color-rose-900: #881337; + --color-rose-950: #4c0519; + + --color-brand-grad: radial-gradient(109.26% 109.26% at 49.83% 13.37%, #ff343c 0%, #f06f53 100%); + --color-brand: #fa4e49; + + --transition-duration-250: 250ms; +} + +/* + The default border color has changed to `currentcolor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } +} diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx index 5c9467ada..ffa82b7dc 100644 --- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -1,19 +1,19 @@ -import { useStoreState } from 'easy-peasy'; import type { FormikHelpers } from 'formik'; import { Formik } from 'formik'; -import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import Reaptcha from 'reaptcha'; import { object, string } from 'yup'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import Button from '@/components/elements/Button'; +import Captcha, { getCaptchaResponse } from '@/components/elements/Captcha'; import ContentBox from '@/components/elements/ContentBox'; import Field from '@/components/elements/Field'; -import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail'; +import CaptchaManager from '@/lib/captcha'; + import { httpErrorToHuman } from '@/api/http'; +import http from '@/api/http'; import useFlash from '@/plugins/useFlash'; @@ -23,45 +23,31 @@ interface Values { email: string; } -export default () => { +const ForgotPasswordContainer = () => { + const { t } = useTranslation(); - const ref = useRef(null); - const [token, setToken] = useState(''); - const { clearFlashes, addFlash } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); - - useEffect(() => { - clearFlashes(); - }, []); const handleSubmission = ({ email }: Values, { setSubmitting, resetForm }: FormikHelpers) => { clearFlashes(); - // If there is no token in the state yet, request the token and then abort this submit request - // since it will be re-submitted when the recaptcha data is returned by the component. - if (recaptchaEnabled && !token) { - ref.current!.execute().catch((error) => { - console.error(error); + const captchaResponse = getCaptchaResponse(); - setSubmitting(false); - addFlash({ - type: 'error', - title: t('common.error'), - message: httpErrorToHuman(error), - }); - }); - - return; + let requestData: any = { email }; + if (CaptchaManager.isEnabled() && captchaResponse) { + const fieldName = CaptchaManager.getProviderInstance().getResponseFieldName(); + if (fieldName) { + requestData = { ...requestData, [fieldName]: captchaResponse }; + } } - requestPasswordResetEmail(email, token) + http.post('/auth/password', requestData) .then((response) => { resetForm(); addFlash({ type: 'success', title: t('common.success'), - message: response, + message: response.data.status || t('auth.email_sent'), }); }) .catch((error) => { @@ -72,12 +58,7 @@ export default () => { message: httpErrorToHuman(error), }); }) - .then(() => { - setToken(''); - if (ref.current !== null) { - void ref.current.reset(); - } - + .finally(() => { setSubmitting(false); }); }; @@ -93,7 +74,7 @@ export default () => { .required(t('auth.validation.email_required_reset')), })} > - {({ isSubmitting, setSubmitting, submitForm }) => ( + {({ isSubmitting }) => (
@@ -101,45 +82,41 @@ export default () => {
-

{t('auth.reset_password')}

-
{t('auth.reset_email_info')}
- -
+

Reset Password

+
{t('auth.email.reset_email_info')}
+ + + { + console.error(t('auth.captcha.captcha_error'), error); + addFlash({ + type: 'error', + title: t('common.error'), + message: t('auth.captcha.verification_failed'), + }); + }} + /> + +
- {recaptchaEnabled && ( - { - setToken(response); - setTimeout(() => { - submitForm(); - }, 0); - }} - onExpire={() => { - setSubmitting(false); - setToken(''); - }} - /> - )} -
+
{t('auth.return_to_login')} @@ -150,3 +127,5 @@ export default () => { ); }; + +export default ForgotPasswordContainer; diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index a2d3e7c72..ed9fbd9aa 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -29,16 +29,13 @@ type Props = OwnProps & { clearAndAddHttpError: ActionCreator; }; -/** - * Component hiển thị form xác thực hai yếu tố khi đăng nhập - */ -function LoginCheckpointContainer() { +function LoginCheckpointForm() { const { t } = useTranslation(); const { isSubmitting, setFieldValue } = useFormikContext(); const [isMissingDevice, setIsMissingDevice] = useState(false); return ( - +
@@ -51,7 +48,7 @@ function LoginCheckpointContainer() {
{ @@ -86,17 +83,22 @@ function LoginCheckpointContainer() { setFieldValue('recoveryCode', ''); setIsMissingDevice((s) => !s); }} - className={`cursor-pointer text-xs text-white tracking-wide uppercase no-underline hover:text-neutral-700`} + // className={`cursor-pointer text-xs text-white tracking-wide uppercase no-underline hover:text-neutral-700`} + className={ + 'block w-full text-center py-2.5 px-4 text-xs font-medium tracking-wide uppercase text-white hover:text-white/80 transition-colors duration-200 border border-white/20 rounded-full hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-white/30' + } > {!isMissingDevice ? t('auth.lost_device') : t('auth.have_device')}
{t('auth.return_to_login')} @@ -111,7 +113,6 @@ const EnhancedForm = withFormik({ loginCheckpoint(location.state?.token || '', code, recoveryCode) .then((response) => { if (response.complete) { - // @ts-expect-error this is valid window.location = response.intended || '/'; return; } @@ -129,9 +130,9 @@ const EnhancedForm = withFormik({ code: '', recoveryCode: '', }), -})(LoginCheckpointContainer); +})(LoginCheckpointForm); -export default ({ ...props }: OwnProps) => { +const LoginCheckpointContainer = ({ ...props }: OwnProps) => { const { clearAndAddHttpError } = useFlash(); const location = useLocation(); @@ -145,3 +146,5 @@ export default ({ ...props }: OwnProps) => { return ; }; + +export default LoginCheckpointContainer; diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 15fba5383..c010c0315 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -1,35 +1,31 @@ -import { useStoreState } from 'easy-peasy'; import type { FormikHelpers } from 'formik'; import { Formik } from 'formik'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useNavigate } from 'react-router-dom'; -import Reaptcha from 'reaptcha'; import { object, string } from 'yup'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import Button from '@/components/elements/Button'; +import Captcha, { getCaptchaResponse } from '@/components/elements/Captcha'; import Field from '@/components/elements/Field'; +import Logo from '@/components/elements/PyroLogo'; + +import CaptchaManager from '@/lib/captcha'; import login from '@/api/auth/login'; import useFlash from '@/plugins/useFlash'; -import Logo from '../elements/PyroLogo'; - interface Values { - username: string; + user: string; password: string; } function LoginContainer() { const { t } = useTranslation(); - const ref = useRef(null); - const [token, setToken] = useState(''); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); - const navigate = useNavigate(); useEffect(() => { @@ -39,72 +35,70 @@ function LoginContainer() { const onSubmit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes(); - // If there is no token in the state yet, request the token and then abort this submit request - // since it will be re-submitted when the recaptcha data is returned by the component. - if (recaptchaEnabled && !token) { - ref.current!.execute().catch((error) => { - console.error(error); + // Get captcha response if enabled + let loginData: any = values; + if (CaptchaManager.isEnabled()) { + const captchaResponse = getCaptchaResponse(); + const fieldName = CaptchaManager.getProviderInstance().getResponseFieldName(); - setSubmitting(false); - clearAndAddHttpError({ error }); - }); + console.log('Captcha enabled, response:', captchaResponse, 'fieldName:', fieldName); - return; - } - - login({ ...values, recaptchaData: token }) - .then((response) => { - if (response.complete) { - // @ts-expect-error this is valid - window.location = response.intended || '/'; + if (fieldName) { + if (captchaResponse) { + loginData = { ...values, [fieldName]: captchaResponse }; + console.log('Adding captcha to login data:', loginData); + } else { + console.error(t('auth.captcha.no_response')); + clearAndAddHttpError({ + error: new Error(t('auth.captcha.please_complete')), + }); + setSubmitting(false); return; } + } + } else { + console.log('Captcha not enabled'); + } - navigate('/auth/login/checkpoint', { state: { token: response.confirmationToken } }); - }) - .catch((error) => { - console.error(error); - - setToken(''); - // https://github.com/jsardev/reaptcha/issues/218 - if (ref.current) { - setTimeout(() => { - if (ref.current) { - ref.current.reset(); - } - }, 500); + login(loginData) + .then((response) => { + if (response.complete) { + window.location.href = response.intended || '/'; + return; } - + navigate('/auth/login/checkpoint', { + state: { token: response.confirmationToken }, + }); + }) + .catch((error: any) => { setSubmitting(false); - clearAndAddHttpError({ error }); + + if (error.code === 'InvalidCredentials') { + clearAndAddHttpError({ + error: new Error(t('auth.validation.invalid_username_password')), + }); + } else if (error.code === 'DisplayException') { + clearAndAddHttpError({ + error: new Error(error.detail || error.message), + }); + } else { + clearAndAddHttpError({ error }); + } }); }; return ( - {({ isSubmitting, setSubmitting, submitForm }) => ( + {({ isSubmitting, isValid }) => (
- {/* temp src */} - {/* */} - {/* */}
@@ -113,7 +107,7 @@ function LoginContainer() { id='usernameOrEmail' type={'text'} label={t('auth.username_or_email')} - name={'username'} + name={'user'} disabled={isSubmitting} />
@@ -131,33 +125,28 @@ function LoginContainer() { {t('auth.forgot_password')}
+ + { + console.error('Captcha error:', error); + clearAndAddHttpError({ + error: new Error(t('auth.captcha.verification_failed')), + }); + }} + /> +
- {recaptchaEnabled && ( - { - setToken(response); - // Ensure submitForm is called after token is updated - setTimeout(submitForm, 100); - }} - onExpire={() => { - setSubmitting(false); - setToken(''); - }} - /> - )}
)}
diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx index 25162bd69..379ea6da3 100644 --- a/resources/scripts/components/auth/LoginFormContainer.tsx +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -4,25 +4,35 @@ import { useTranslation } from 'react-i18next'; import FlashMessageRender from '@/components/FlashMessageRender'; -type Props = React.DetailedHTMLProps, HTMLFormElement> & { - title?: string; +type Props = React.DetailedHTMLProps< + React.FormHTMLAttributes, + HTMLFormElement +> & { + title?: string; }; -export default forwardRef(({ title, ...props }, ref) => { +const LoginFormContainer = forwardRef( + ({ title, ...props }, ref) => { const { t } = useTranslation(); + return ( -
- {title && ( -

- {title.startsWith('auth.') ? t(title) : title} -

- )} - -
-
-
{props.children}
-
-
-
+
+ {title && ( +

+ {title.startsWith('auth.') ? t(title) : title} +

+ )} + +
+
+
{props.children}
+
+
+
); -}); + } +); + +LoginFormContainer.displayName = 'LoginFormContainer'; + +export default LoginFormContainer; diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx index 96972361a..93751a3a5 100644 --- a/resources/scripts/components/auth/ResetPasswordContainer.tsx +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -1,33 +1,38 @@ -import { Actions, useStoreActions } from 'easy-peasy'; import { Formik, FormikHelpers } from 'formik'; -import { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useEffect, useState } from 'react'; import { Link, useParams } from 'react-router-dom'; import { object, ref, string } from 'yup'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import Button from '@/components/elements/Button'; +import Captcha, { getCaptchaResponse } from '@/components/elements/Captcha'; import ContentBox from '@/components/elements/ContentBox'; import Field from '@/components/elements/Field'; import Input from '@/components/elements/Input'; -import performPasswordReset from '@/api/auth/performPasswordReset'; -import { httpErrorToHuman } from '@/api/http'; +import CaptchaManager from '@/lib/captcha'; -import { ApplicationStore } from '@/state'; +import performPasswordReset from '@/api/auth/performPasswordReset'; + +import useFlash from '@/plugins/useFlash'; import Logo from '../elements/PyroLogo'; interface Values { password: string; - passwordConfirmation: string; + password_confirmation: string; } function ResetPasswordContainer() { const { t } = useTranslation(); const [email, setEmail] = useState(''); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + useEffect(() => { + clearFlashes(); + }, []); const parsed = new URLSearchParams(location.search); if (email.length === 0 && parsed.get('email')) { @@ -36,9 +41,38 @@ function ResetPasswordContainer() { const params = useParams<'token'>(); - const submit = ({ password, passwordConfirmation }: Values, { setSubmitting }: FormikHelpers) => { + const submit = ({ password, password_confirmation }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes(); - performPasswordReset(email, { token: params.token ?? '', password, passwordConfirmation }) + + // Get captcha response if enabled + const captchaResponse = getCaptchaResponse(); + + let resetData: any = { token: params.token ?? '', password, password_confirmation }; + if (CaptchaManager.isEnabled()) { + const fieldName = CaptchaManager.getProviderInstance().getResponseFieldName(); + + // console.log('Captcha enabled, response:', captchaResponse, 'fieldName:', fieldName); + + if (fieldName) { + if (captchaResponse) { + resetData = { + ...resetData, + [fieldName]: captchaResponse, + }; + + console.log('Adding captcha to reset data:'); + console.debug(resetData); + } else { + console.error('Captcha enabled but no response available'); + console.log(captchaResponse); + clearAndAddHttpError({ error: new Error(t('auth.captcha.please_complete')) }); + setSubmitting(false); + return; + } + } + } + + performPasswordReset(email, resetData) .then(() => { // @ts-expect-error this is valid window.location = '/'; @@ -47,7 +81,9 @@ function ResetPasswordContainer() { console.error(error); setSubmitting(false); - addFlash({ type: 'error', title: t('common.error'), message: httpErrorToHuman(error) }); + clearAndAddHttpError({ + error: new Error(error), + }); }); }; @@ -57,7 +93,7 @@ function ResetPasswordContainer() { onSubmit={submit} initialValues={{ password: '', - passwordConfirmation: '', + password_confirmation: '', }} validationSchema={object().shape({ password: string() @@ -78,8 +114,7 @@ function ResetPasswordContainer() {
- {/* */} - +
+ { + console.error('Captcha error:', error); + clearAndAddHttpError({ + error: new Error('Captcha verification failed. Please try again.'), + }); + }} + /> +
{ +interface CreateValues { + description: string; + allowedIps: string; +} + +const AccountApiContainer = () => { const { t } = useTranslation(); + const [deleteIdentifier, setDeleteIdentifier] = useState(''); const [keys, setKeys] = useState([]); const [loading, setLoading] = useState(true); - const { clearAndAddHttpError } = useFlashKey('account'); + const [showCreateModal, setShowCreateModal] = useState(false); + const [apiKey, setApiKey] = useState(''); + const [showKeys, setShowKeys] = useState>({}); + + const { clearAndAddHttpError } = useFlashKey('api-keys'); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); useEffect(() => { getApiKeys() @@ -33,7 +57,6 @@ export default () => { const doDeletion = (identifier: string) => { setLoading(true); - clearAndAddHttpError(); deleteApiKey(identifier) .then(() => setKeys((s) => [...(s || []).filter((key) => key.identifier !== identifier)])) @@ -44,58 +67,213 @@ export default () => { }); }; - return ( - - {/* Flash messages will now appear at the top of the page */} - -
- - setKeys((s) => [...s!, key])} /> - -
- - - setDeleteIdentifier('')} - onConfirmed={() => doDeletion(deleteIdentifier)} - > - {t('api.delete_api_key_desc', { key: deleteIdentifier })} - + const submitCreate = (values: CreateValues, { setSubmitting, resetForm }: FormikHelpers) => { + clearFlashes('account'); + createApiKey(values.description, values.allowedIps) + .then(({ secretToken, ...key }) => { + resetForm(); + setSubmitting(false); + setApiKey(`${key.identifier}${secretToken}`); + setKeys((s) => [...s!, key]); + setShowCreateModal(false); + }) + .catch((error) => { + console.error(error); + addError({ key: 'account', message: httpErrorToHuman(error) }); + setSubmitting(false); + }); + }; - {keys.length === 0 ? ( -

- {loading ? t('common.loading') : t('api.no_api_keys')} -

- ) : ( - keys.map((key) => ( -
-
-
-

{key.description}

-

- {t('api.last_used')}{' '} - {key.lastUsedAt ? format(key.lastUsedAt, 'MMM d, yyyy HH:mm') : t('api.never')} -

-
-

- - {key.identifier} - -

- + + + + + + + +
); }; + +export default AccountApiContainer; diff --git a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx index d991c4636..e662a5ec0 100644 --- a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx +++ b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx @@ -11,48 +11,85 @@ import PageContentBlock from '@/components/elements/PageContentBlock'; import Code from '../elements/Code'; -export default () => { +const AccountOverviewContainer = () => { const { t } = useTranslation(); const { state } = useLocation(); return ( -

- {t('settings.your_settings')} -

- {state?.twoFactorRedirect && ( - - {t('settings.two_factor_error')} - - )} - -
-

{t('settings.account_info')}

- - - -

{t('settings.password_auth')}

- - - - - - - - - -

{t('settings.app')}

- -

{t('settings.panel_version_desc')}

-
- {import.meta.env.VITE_PYRODACTYL_VERSION} - - {t('build')} {import.meta.env.VITE_PYRODACTYL_BUILD_NUMBER}, {t('commit')}{' '} - {import.meta.env.VITE_COMMIT_HASH.slice(0, 7)} - +
+ {state?.twoFactorRedirect && ( +
+ + {t('settings.two_factor_error')} +
- + )} + +
+
+ + + +
+ +
+
+ + + + + + +
+
+ +
+ +

+ {t('settings.panel_version_desc')} +

+
+ + {t('settings.version')}: {import.meta.env.VITE_PYRODACTYL_VERSION} -{' '} + {import.meta.env.VITE_BRANCH_NAME} + + {t('settings.commit')} : {import.meta.env.VITE_COMMIT_HASH.slice(0, 7)} +
+
+
+
); }; + +export default AccountOverviewContainer; diff --git a/resources/scripts/components/dashboard/ApiKeyModal.tsx b/resources/scripts/components/dashboard/ApiKeyModal.tsx index b589d3326..d4cb82c5e 100644 --- a/resources/scripts/components/dashboard/ApiKeyModal.tsx +++ b/resources/scripts/components/dashboard/ApiKeyModal.tsx @@ -17,7 +17,7 @@ const ApiKeyModal = ({ apiKey }: Props) => { const { t } = useTranslation(); return ( -
+
{/* Flash message section */} @@ -41,7 +41,7 @@ const ApiKeyModal = ({ apiKey }: Props) => { diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 49a15edc5..294c8f063 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -1,5 +1,6 @@ import { useStoreState } from 'easy-peasy'; import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; import useSWR from 'swr'; @@ -13,6 +14,7 @@ import { import PageContentBlock from '@/components/elements/PageContentBlock'; import Pagination from '@/components/elements/Pagination'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/elements/Tabs'; +import { PageListContainer } from '@/components/elements/pages/PageList'; import getServers from '@/api/getServers'; import { PaginatedResult } from '@/api/http'; @@ -23,7 +25,9 @@ import { usePersistedState } from '@/plugins/usePersistedState'; import { MainPageHeader } from '../elements/MainPageHeader'; -export default () => { +const DashboardContainer = () => { + const { t } = useTranslation(); + const { search } = useLocation(); const defaultPage = Number(new URLSearchParams(search).get('page') || '1'); @@ -64,160 +68,226 @@ export default () => { return ( - { - setDashboardDisplayOption(value); - }} - className='w-full' - > - -
- - - - - -
More filters coming soon!
- {rootAdmin && ( - { - setShowOnlyAdmin((s) => !s); - }} - > - {showOnlyAdmin ? 'Show personal servers' : 'Show other servers'} - - )} -
-
- - - - - - - - - - - - -
-
- {!servers ? ( - <> - ) : ( - <> - - - {({ items }) => - items.length > 0 ? ( - items.map((server, index) => ( -
+ { + setDashboardDisplayOption(value); + }} + className='w-full' + > +
+ +
+ + + + + + {rootAdmin && ( + { + setShowOnlyAdmin((s) => !s); }} > - -
- )) - ) : ( -

- {showOnlyAdmin - ? 'There are no other servers to display.' - : 'There are no servers associated with your account.'} -

- ) - } - - - -
+ {showOnlyAdmin ? t('home.show_personal') : t('home.show_others')} + + )} + + + + + + + + + + + + + + +
+
+
+ {!servers ? ( +
+
+
+ ) : ( + <> + {({ items }) => items.length > 0 ? ( - items.map((server, index) => ( -
- + {items.map((server, index) => ( +
-
- )) + className='transform-gpu skeleton-anim-2' + style={{ + animationDelay: `${index * 50 + 50}ms`, + animationTimingFunction: + 'linear(0,0.01,0.04 1.6%,0.161 3.3%,0.816 9.4%,1.046,1.189 14.4%,1.231,1.254 17%,1.259,1.257 18.6%,1.236,1.194 22.3%,1.057 27%,0.999 29.4%,0.955 32.1%,0.942,0.935 34.9%,0.933,0.939 38.4%,1 47.3%,1.011,1.017 52.6%,1.016 56.4%,1 65.2%,0.996 70.2%,1.001 87.2%,1)', + }} + > + +
+ ))} + ) : ( -

- {showOnlyAdmin - ? 'There are no other servers to display.' - : 'There are no servers associated with your account.'} -

+
+
+
+ + + +
+

+ {showOnlyAdmin + ? t('home.no_others_found') + : t('home.no_servers_found')} +

+

+ {showOnlyAdmin + ? t('home.no_display_servers') + : t('home.no_associated_servers')} +

+
+
) }
-
-
- - )} -
+ + + + {({ items }) => + items.length > 0 ? ( +
+ {items.map((server, index) => ( +
+ +
+ ))} +
+ ) : ( +
+
+
+ + + +
+

+ {showOnlyAdmin + ? t('home.no_others_found') + : t('home.no_servers_found')} +

+

+ {showOnlyAdmin + ? t('home.no_display_servers') + : t('home.no_associated_servers')} +

+
+
+ ) + } +
+
+ + )} + +
); }; + +export default DashboardContainer; diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index 06443e3ab..9a42c7f6c 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -1,4 +1,5 @@ import { Fragment, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; @@ -12,9 +13,8 @@ import getServerResourceUsage, { ServerPowerState, ServerStats } from '@/api/ser const isAlarmState = (current: number, limit: number): boolean => limit > 0 && current / (limit * 1024 * 1024) >= 0.9; const StatusIndicatorBox = styled.div<{ $status: ServerPowerState | undefined }>` - // background: linear-gradient(180deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.044) 100%); - border: 1px solid #ffffff07; background: #ffffff11; + border: 1px solid #ffffff12; transition: all 250ms ease-in-out; padding: 1.75rem 2rem; cursor: pointer; @@ -25,8 +25,9 @@ const StatusIndicatorBox = styled.div<{ $status: ServerPowerState | undefined }> position: relative; &:hover { - border: 1px solid #ffffff11; - background: #ffffff18; + border: 1px solid #ffffff19; + background: #ffffff19; + transition-duration: 0ms; } & .status-bar { @@ -57,7 +58,9 @@ const StatusIndicatorBox = styled.div<{ $status: ServerPowerState | undefined }> type Timer = ReturnType; -export default ({ server, className }: { server: Server; className?: string }) => { +const ServerRow = ({ server, className }: { server: Server; className?: string }) => { + const { t } = useTranslation(); + const interval = useRef(null) as React.MutableRefObject; const [isSuspended, setIsSuspended] = useState(server.status === 'suspended'); const [stats, setStats] = useState(null); @@ -81,7 +84,7 @@ export default ({ server, className }: { server: Server; className?: string }) = }); return () => { - interval.current && clearInterval(interval.current); + if (interval.current) clearInterval(interval.current); }; }, [isSuspended]); @@ -121,7 +124,11 @@ export default ({ server, className }: { server: Server; className?: string }) =
); }; +// For the displayName, you can either use a static string or create a wrapper component ConfirmationModal.displayName = 'ConfirmationModal'; +// Create a wrapper component to handle the modal with translations if needed +const ConfirmationModalWrapper = (props: Props) => { + return ; +}; + export default asModal((props) => ({ title: props.title, showSpinnerOverlay: props.showSpinnerOverlay, -}))(ConfirmationModal); +}))(ConfirmationModalWrapper); diff --git a/resources/scripts/components/elements/ContentBox.tsx b/resources/scripts/components/elements/ContentBox.tsx index dde5afe08..00006596b 100644 --- a/resources/scripts/components/elements/ContentBox.tsx +++ b/resources/scripts/components/elements/ContentBox.tsx @@ -11,7 +11,7 @@ type Props = Readonly< >; const ContentBox = ({ title, showFlashes, showLoadingOverlay, children, ...props }: Props) => ( -
+
{title &&

{title}

} {showFlashes && }
diff --git a/resources/scripts/components/elements/ContextMenu.tsx b/resources/scripts/components/elements/ContextMenu.tsx index 0331cec51..b086e55fb 100644 --- a/resources/scripts/components/elements/ContextMenu.tsx +++ b/resources/scripts/components/elements/ContextMenu.tsx @@ -1,7 +1,10 @@ import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'; -import { CheckIcon, ChevronRightIcon, DotFilledIcon } from '@radix-ui/react-icons'; import * as React from 'react'; +import HugeIconsCheck from '@/components/elements/hugeicons/Check'; +import HugeIconsChevronRight from '@/components/elements/hugeicons/ChevronRight'; +import HugeIconsDotFilled from '@/components/elements/hugeicons/DotFilled'; + import { cn } from '@/lib/utils'; const ContextMenu = ContextMenuPrimitive.Root; @@ -25,14 +28,14 @@ const ContextMenuSubTrigger = React.forwardRef< {children} - + )); ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName; @@ -44,7 +47,7 @@ const ContextMenuSubContent = React.forwardRef< - + {children} @@ -117,14 +120,14 @@ const ContextMenuRadioItem = React.forwardRef< - + {children} diff --git a/resources/scripts/components/elements/CopyOnClick.tsx b/resources/scripts/components/elements/CopyOnClick.tsx index 42545f61c..dc697eb9e 100644 --- a/resources/scripts/components/elements/CopyOnClick.tsx +++ b/resources/scripts/components/elements/CopyOnClick.tsx @@ -41,7 +41,7 @@ const CopyOnClick = ({ text, children, showInNotification }: CopyOnClickProps) = const child = !text ? React.Children.only(children) : React.cloneElement(React.Children.only(children), { - // @ts-ignore + // @ts-expect-error - Props type inference issue with React.cloneElement className: clsx(children.props.className || '', 'cursor-pointer'), onClick: (e: React.MouseEvent) => { copy(String(text)); diff --git a/resources/scripts/components/elements/DropdownMenu.tsx b/resources/scripts/components/elements/DropdownMenu.tsx index a2140bf91..3d530af2d 100644 --- a/resources/scripts/components/elements/DropdownMenu.tsx +++ b/resources/scripts/components/elements/DropdownMenu.tsx @@ -1,9 +1,11 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; -import { CheckIcon, ChevronRightIcon } from '@radix-ui/react-icons'; import * as React from 'react'; import { cn } from '@/lib/utils'; +import HugeIconsCheck from './hugeicons/Check'; +import HugeIconsChevronRight from './hugeicons/ChevronRight'; + const DropdownMenu = DropdownMenuPrimitive.Root; const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; @@ -25,14 +27,14 @@ const DropdownMenuSubTrigger = React.forwardRef< {children} - + )); DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; @@ -44,7 +46,7 @@ const DropdownMenuSubContent = React.forwardRef< - + {children} @@ -119,7 +121,7 @@ const DropdownMenuRadioItem = React.forwardRef< string; +} + interface State { hasError: boolean; } -interface ErrorBoundaryProps { - t: (key: string) => string; -} - -/** - * Component bắt lỗi React và hiển thị thông báo lỗi thân thiện - */ -class ErrorBoundary extends Component { +class ErrorBoundary extends Component { override state: State = { hasError: false, }; @@ -27,16 +25,19 @@ class ErrorBoundary extends Component { override render() { const { t } = this.props; - - return this.state.hasError ? ( -
-
-

{t('errors.rendering_error')}

+ if (this.state.hasError) { + return ( +
+
+

+ {t('errors.rendering_error')} +

+
-
- ) : ( - (this.props as any).children - ); + ); + } + + return this.props.children; } } diff --git a/resources/scripts/components/elements/Field.tsx b/resources/scripts/components/elements/Field.tsx index bbe6987c9..b18acccfc 100644 --- a/resources/scripts/components/elements/Field.tsx +++ b/resources/scripts/components/elements/Field.tsx @@ -21,7 +21,7 @@ const Field = forwardRef( )} ` + appearance: none; + width: 1rem; + height: 1rem; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 0.25rem; + background-color: rgba(255, 255, 255, 0.09); color-adjust: exact; background-origin: border-box; transition: @@ -13,13 +19,27 @@ const checkboxStyle = css` box-shadow 25ms linear; &:checked { - background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); - background-color: white; + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); + background-color: #3b82f6; + border-color: #3b82f6; background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + } + + &:hover { + border-color: rgba(255, 255, 255, 0.3); } &:focus { - box-shadow: 0 0 0 1px rgba(9, 103, 210, 0.25); + outline: none; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25); + border-color: #3b82f6; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; } `; diff --git a/resources/scripts/components/elements/ItemContainer.tsx b/resources/scripts/components/elements/ItemContainer.tsx index 6e4d5bb7f..18b923544 100644 --- a/resources/scripts/components/elements/ItemContainer.tsx +++ b/resources/scripts/components/elements/ItemContainer.tsx @@ -1,6 +1,4 @@ -import { IconDefinition } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useMemo } from 'react'; +import React, { useMemo } from 'react'; import { v4 } from 'uuid'; import CopyOnClick from './CopyOnClick'; @@ -9,7 +7,7 @@ export interface ContainerProps { title: string; description: string; children?: React.ReactNode; - icon?: IconDefinition; + icon?: React.ComponentType<{ className?: string; fill?: string }>; labelClasses?: string; titleClasses?: string; descriptionClasses?: string; @@ -36,7 +34,7 @@ const ItemContainer = ({ > {icon && ( )}
diff --git a/resources/scripts/components/elements/MainPageHeader.tsx b/resources/scripts/components/elements/MainPageHeader.tsx index a9522459b..98ae0d778 100644 --- a/resources/scripts/components/elements/MainPageHeader.tsx +++ b/resources/scripts/components/elements/MainPageHeader.tsx @@ -1,4 +1,5 @@ import clsx from 'clsx'; +import { JSX } from 'react'; import styled from 'styled-components'; const HeaderWrapper = styled.div``; @@ -17,19 +18,25 @@ export const MainPageHeader: React.FC = ({ direction = 'row', }) => { return ( - -
-

{title}

- {titleChildren} + +
+
+

+ {title} +

+
+ {titleChildren &&
{titleChildren}
}
- {children} + {direction === 'column' && children &&
{children}
}
); }; diff --git a/resources/scripts/components/elements/MainSidebar.tsx b/resources/scripts/components/elements/MainSidebar.tsx index f201e5ced..84bfe89a9 100644 --- a/resources/scripts/components/elements/MainSidebar.tsx +++ b/resources/scripts/components/elements/MainSidebar.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components'; const MainSidebar = styled.nav` width: 300px; flex-direction: column; - flex-shrink: 0; + shrink: 0; border-radius: 8px; overflow-x: hidden; padding: 32px; diff --git a/resources/scripts/components/elements/MobileFullScreenMenu.tsx b/resources/scripts/components/elements/MobileFullScreenMenu.tsx new file mode 100644 index 000000000..cd7049a44 --- /dev/null +++ b/resources/scripts/components/elements/MobileFullScreenMenu.tsx @@ -0,0 +1,229 @@ +import React from 'react'; +import { NavLink } from 'react-router-dom'; + +import Can from '@/components/elements/Can'; +import HugeIconsApi from '@/components/elements/hugeicons/Api'; +import HugeIconsClock from '@/components/elements/hugeicons/Clock'; +import HugeIconsCloudUp from '@/components/elements/hugeicons/CloudUp'; +import HugeIconsConnections from '@/components/elements/hugeicons/Connections'; +import HugeIconsConsole from '@/components/elements/hugeicons/Console'; +import HugeIconsController from '@/components/elements/hugeicons/Controller'; +import HugeIconsDashboardSettings from '@/components/elements/hugeicons/DashboardSettings'; +import HugeIconsDatabase from '@/components/elements/hugeicons/Database'; +import HugeIconsFolder from '@/components/elements/hugeicons/Folder'; +import HugeIconsHome from '@/components/elements/hugeicons/Home'; +import HugeIconsPencil from '@/components/elements/hugeicons/Pencil'; +import HugeIconsPeople from '@/components/elements/hugeicons/People'; +import HugeIconsSsh from '@/components/elements/hugeicons/Ssh'; +import HugeIconsX from '@/components/elements/hugeicons/X'; + +interface MobileFullScreenMenuProps { + isVisible: boolean; + onClose: () => void; + children: React.ReactNode; +} + +const MobileFullScreenMenu = ({ isVisible, onClose, children }: MobileFullScreenMenuProps) => { + if (!isVisible) return null; + + return ( +
+ {/* Close button */} + + + {/* Full screen navigation menu */} +
+
+ {/* Menu items */} + +
+
+
+ ); +}; + +interface DashboardMobileMenuProps { + isVisible: boolean; + onClose: () => void; +} + +export const DashboardMobileMenu = ({ isVisible, onClose }: DashboardMobileMenuProps) => { + const NavigationItem = ({ + to, + icon: Icon, + children, + end = false, + }: { + to: string; + icon: React.ComponentType<{ fill: string }>; + children: React.ReactNode; + end?: boolean; + }) => ( + + `flex items-center gap-4 p-4 rounded-md transition-all duration-200 ${ + isActive + ? 'bg-gradient-to-r from-brand/20 to-brand/10 border-l-4 border-brand text-white' + : 'text-white/80 hover:text-white hover:bg-[#ffffff11] border-l-4 border-transparent' + }` + } + onClick={onClose} + > + + {children} + + ); + + return ( + + + Servers + + + API Keys + + + SSH Keys + + + Settings + + + ); +}; + +interface ServerMobileMenuProps { + isVisible: boolean; + onClose: () => void; + serverId?: string; + databaseLimit?: number; + backupLimit?: number; + allocationLimit?: number; + subdomainSupported?: boolean; +} + +export const ServerMobileMenu = ({ + isVisible, + onClose, + serverId, + databaseLimit = 0, + backupLimit = 0, + allocationLimit = 0, + subdomainSupported = false, +}: ServerMobileMenuProps) => { + const NavigationItem = ({ + to, + icon: Icon, + children, + end = false, + }: { + to: string; + icon: React.ComponentType<{ fill: string }>; + children: React.ReactNode; + end?: boolean; + }) => ( + + `flex items-center gap-4 p-4 rounded-md transition-all duration-200 ${ + isActive + ? 'bg-gradient-to-r from-brand/20 to-brand/10 border-l-4 border-brand text-white' + : 'text-white/80 hover:text-white hover:bg-[#ffffff11] border-l-4 border-transparent' + }` + } + onClick={onClose} + > + + {children} + + ); + + if (!serverId) return null; + + return ( + + + Home + + + <> + + + Files + + + + {databaseLimit > 0 && ( + + + Databases + + + )} + + {backupLimit > 0 && ( + + + Backups + + + )} + + {(allocationLimit > 0 || subdomainSupported) && ( + + + Networking + + + )} + + + + Users + + + + + + Startup + + + + + + Schedules + + + + + + Settings + + + + + + Activity + + + + + + + Software + + + + ); +}; + +export default MobileFullScreenMenu; diff --git a/resources/scripts/components/elements/MobileTopBar.tsx b/resources/scripts/components/elements/MobileTopBar.tsx new file mode 100644 index 000000000..6d79e3ad9 --- /dev/null +++ b/resources/scripts/components/elements/MobileTopBar.tsx @@ -0,0 +1,102 @@ +import { NavLink } from 'react-router-dom'; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/elements/DropdownMenu'; +import Logo from '@/components/elements/PyroLogo'; +import HugeIconsMenu from '@/components/elements/hugeicons/Menu'; + +interface MobileTopBarProps { + onMenuToggle: () => void; + onTriggerLogout: () => void; + onSelectAdminPanel?: () => void; + rootAdmin?: boolean; +} + +const MobileTopBar = ({ onMenuToggle, onTriggerLogout, onSelectAdminPanel, rootAdmin }: MobileTopBarProps) => { + const handleMenuToggle = () => { + try { + if (onMenuToggle && typeof onMenuToggle === 'function') { + onMenuToggle(); + } + } catch (error) { + console.error('Error in mobile menu toggle:', error); + } + }; + + const handleLogout = () => { + try { + if (onTriggerLogout && typeof onTriggerLogout === 'function') { + onTriggerLogout(); + } + } catch (error) { + console.error('Error in logout trigger:', error); + } + }; + + const handleAdminPanel = () => { + try { + if (onSelectAdminPanel && typeof onSelectAdminPanel === 'function') { + onSelectAdminPanel(); + } + } catch (error) { + console.error('Error in admin panel select:', error); + } + }; + + return ( +
+ {/* Logo */} + + + + +
+ {/* User Menu */} + + + + + + {rootAdmin && onSelectAdminPanel && ( + + Admin Panel + + Staff + + + )} + + Log Out + + + + {/* Menu Toggle Button */} + +
+
+ ); +}; + +export default MobileTopBar; diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx index e69d30046..46ecd0868 100644 --- a/resources/scripts/components/elements/Modal.tsx +++ b/resources/scripts/components/elements/Modal.tsx @@ -1,11 +1,11 @@ import { Dialog as HDialog } from '@headlessui/react'; -import { AnimatePresence, motion } from 'framer-motion'; +import { AnimatePresence, motion } from 'motion/react'; import { useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; +import ActionButton from '@/components/elements/ActionButton'; import Spinner from '@/components/elements/Spinner'; -import { Button } from '@/components/elements/button/index'; import { DialogContext, IconPosition, styles } from '@/components/elements/dialog'; import HugeIconsX from './hugeicons/X'; @@ -99,7 +99,7 @@ const Modal: React.FC = ({ <> {showSpinnerOverlay && (
@@ -123,12 +123,12 @@ const Modal: React.FC = ({ background: 'radial-gradient(50% 50% at 50% 50%, rgba(0, 0, 0, 0.42) 0%, rgba(0, 0, 0, 0.94) 100%)', }} - className={'fixed inset-0 backdrop-blur-sm z-[9997]'} + className={'fixed inset-0 backdrop-blur-xs z-9997'} /> -
+
@@ -145,7 +145,7 @@ const Modal: React.FC = ({ {dismissable && ( @@ -166,9 +166,9 @@ const Modal: React.FC = ({
{closeButton && (
- +
)}
diff --git a/resources/scripts/components/elements/Pagination.tsx b/resources/scripts/components/elements/Pagination.tsx index d1dfaef57..3d50ea01a 100644 --- a/resources/scripts/components/elements/Pagination.tsx +++ b/resources/scripts/components/elements/Pagination.tsx @@ -35,7 +35,7 @@ function Pagination({ data: { items, pagination }, onPageSelect, children }: const end = Math.min(pagination.totalPages, pagination.currentPage + 5); for (let i = start; i <= end; i++) { - // @ts-ignore + // @ts-expect-error - Type issue with array push pages.push(i); } @@ -45,7 +45,7 @@ function Pagination({ data: { items, pagination }, onPageSelect, children }: {pages.length > 1 && (
{children}; } - const can = usePermissions(permission); - const { t } = useTranslation(); if (can.filter((p) => p).length > 0) { return <>{children}; diff --git a/resources/scripts/components/elements/PyroLogo.tsx b/resources/scripts/components/elements/PyroLogo.tsx index 092d607b0..2f7e6bba3 100644 --- a/resources/scripts/components/elements/PyroLogo.tsx +++ b/resources/scripts/components/elements/PyroLogo.tsx @@ -1,16 +1,20 @@ // million-ignore -const Logo = () => { +const Logo = ({ className, uniqueId }: { className?: string; uniqueId?: string } = {}) => { + const gradientId = uniqueId + ? `paint0_radial_${uniqueId}` + : `paint0_radial_${Math.random().toString(36).substr(2, 9)}`; + return ( { > { Size: Record<'SMALL' | 'BASE' | 'LARGE', SpinnerSize>; - Suspense: React.FC; + Suspense: React.FC<{ children: React.ReactNode }>; // ✅ Correct } const spin = keyframes` to { transform: rotate(360deg); } `; -// noinspection CssOverwrittenProperties const SpinnerComponent = styled.div` width: 32px; height: 32px; border-width: 3px; border-radius: 50%; animation: ${spin} 1s cubic-bezier(0.55, 0.25, 0.25, 0.7) infinite; + aspect-ratio: 1 / 1; ${(props) => props.size === 'small' @@ -55,12 +55,18 @@ const SpinnerComponent = styled.div` const Spinner: Spinner = ({ centered, visible = true, ...props }) => visible && (centered ? ( -
+
) : ( )); + Spinner.displayName = 'Spinner'; Spinner.Size = { @@ -69,16 +75,8 @@ Spinner.Size = { LARGE: 'large', }; -/** - * Component Suspense kết hợp với Spinner - * @param children Nội dung bên trong - * @param centered Hiển thị spinner chính giữa - * @param size Kích thước spinner - * @param fallback Component hiển thị khi đang loading (mặc định là Spinner) - * @param props Props khác - */ -Spinner.Suspense = ({ children, centered = true, size = Spinner.Size.LARGE, fallback = null, ...props }) => ( - }> +Spinner.Suspense = ({ children }) => ( + }> {children} ); diff --git a/resources/scripts/components/elements/SwitchV2.tsx b/resources/scripts/components/elements/SwitchV2.tsx index f439b288c..18bcbbe80 100644 --- a/resources/scripts/components/elements/SwitchV2.tsx +++ b/resources/scripts/components/elements/SwitchV2.tsx @@ -11,7 +11,7 @@ const Switch = React.forwardRef< >(({ className, ...props }, ref) => ( diff --git a/resources/scripts/components/elements/Tabs.tsx b/resources/scripts/components/elements/Tabs.tsx index eb68483f0..f880bd56d 100644 --- a/resources/scripts/components/elements/Tabs.tsx +++ b/resources/scripts/components/elements/Tabs.tsx @@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef< {} +type InputProps = React.InputHTMLAttributes; const Input = React.forwardRef(({ className, type, ...props }, ref) => { return ( { +const ActivityLogEntry = ({ activity, children }: Props) => { const { pathTo } = useLocationHash(); const actor = activity.relationships.actor; return ( -
-
-
- -
-
-
-
-
- {/* */} - {actor?.username || 'System'} - {/* */} -  —  - - {activity.event} - -
- {activity.isApi && } - {activity.event.startsWith('server:sftp.') && ( - // - - )} - {children} -
+
+ {/* Compact Avatar */} +
+ {actor?.image ? ( + {actor.username + ) : ( +
+ {(actor?.username || 'S').charAt(0).toUpperCase()}
- {!activity.hasAdditionalMetadata && ( -

-

{formatObjectToIdentString(activity.properties)}
-

- )} -
- {activity.ip && ( - - {activity.ip} -  |  + )} +
+ + {/* Main Content - Compact Layout */} +
+
+ {actor?.username || 'System'} + + + {activity.event} + + + {/* Compact badges */} +
+ {activity.isApi && ( + + + API )} - {/* */} - {formatDistanceToNowStrict(activity.timestamp, { addSuffix: true })} - {/* */} + {children}
- {activity.hasAdditionalMetadata && } + + {/* Compact metadata and timestamp */} +
+ {activity.ip && ( + {activity.ip} + )} + {formatDistanceToNowStrict(activity.timestamp, { addSuffix: true })} + + {/* Inline properties for compact view */} + {!activity.hasAdditionalMetadata && + activity.properties && + Object.keys(activity.properties).length > 0 && ( + + {formatObjectToIdentString(activity.properties)} + + )} +
+ + {/* Metadata button */} + {activity.hasAdditionalMetadata && ( +
+ +
+ )}
); }; + +export default ActivityLogEntry; diff --git a/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx b/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx index c293475fb..3bdf5a057 100644 --- a/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx +++ b/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx @@ -1,37 +1,77 @@ import { useState } from 'react'; -import { Button } from '@/components/elements/button/index'; +import ActionButton from '@/components/elements/ActionButton'; import { Dialog } from '@/components/elements/dialog'; -import MetaDataIcon from '@/components/elements/hugeicons/MetaData'; +import HugeIconsCode from '@/components/elements/hugeicons/Code'; +import HugeIconsCopy from '@/components/elements/hugeicons/Copy'; import { formatObjectToIdentString } from '@/lib/objects'; -export default ({ meta }: { meta: Record }) => { +const ActivityLogMetaButton = ({ meta }: { meta: Record }) => { const [open, setOpen] = useState(false); + const [copied, setCopied] = useState(false); + + const copyToClipboard = async () => { + try { + await navigator.clipboard.writeText(JSON.stringify(meta, null, 2)); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy metadata:', err); + } + }; + + const metadataString = formatObjectToIdentString(meta); + const metadataJson = JSON.stringify(meta, null, 2); return ( -
- setOpen(false)} hideCloseIcon title={'Metadata'}> -
-                    {formatObjectToIdentString(meta)}
-                
+ <> + setOpen(false)} hideCloseIcon title={'Event Metadata'}> +
+
+

Formatted View

+ + + {copied ? 'Copied!' : 'Copy JSON'} + +
+ +
+
+                            {metadataString}
+                        
+
+ +
+

Raw JSON

+
+
+                                {metadataJson}
+                            
+
+
+
+ - setOpen(false)}>Close + setOpen(false)}> + Close +
+ -
+ ); }; + +export default ActivityLogMetaButton; diff --git a/resources/scripts/components/elements/activity/style.module.css b/resources/scripts/components/elements/activity/style.module.css index b4449c7d6..a37da7cba 100644 --- a/resources/scripts/components/elements/activity/style.module.css +++ b/resources/scripts/components/elements/activity/style.module.css @@ -1,15 +1,17 @@ +@import '../../../assets/tailwind.css'; + .icons { - @apply flex space-x-1 mx-2 transition-colors duration-100 text-zinc-400; + @apply mx-2 flex space-x-1 text-zinc-400 transition-colors duration-100; & svg { - @apply px-1 py-px cursor-pointer hover:text-zinc-50 h-5 w-auto; + @apply h-5 w-auto cursor-pointer px-1 py-px hover:text-zinc-50; } } .description { - @apply mt-1 text-sm break-words pr-4; + @apply mt-1 pr-4 text-sm break-words; & strong { - @apply text-zinc-50 font-semibold break-all; + @apply font-semibold break-all text-zinc-50; } } diff --git a/resources/scripts/components/elements/alert/Alert.tsx b/resources/scripts/components/elements/alert/Alert.tsx index 52400dbcd..ff309b36f 100644 --- a/resources/scripts/components/elements/alert/Alert.tsx +++ b/resources/scripts/components/elements/alert/Alert.tsx @@ -8,11 +8,11 @@ interface AlertProps { children: React.ReactNode; } -export default ({ type, className, children }: AlertProps) => { +const Alert = ({ type, className, children }: AlertProps) => { return (
{
); }; + +export default Alert; diff --git a/resources/scripts/components/elements/button/Button.tsx b/resources/scripts/components/elements/button/Button.tsx index 38ce69983..326479417 100644 --- a/resources/scripts/components/elements/button/Button.tsx +++ b/resources/scripts/components/elements/button/Button.tsx @@ -45,4 +45,8 @@ const _Button = Object.assign(Button, { Danger: DangerButton, }); +Button.displayName = 'Button'; +TextButton.displayName = 'TextButton'; +DangerButton.displayName = 'DangerButton'; + export default _Button; diff --git a/resources/scripts/components/elements/button/style.module.css b/resources/scripts/components/elements/button/style.module.css index 79e509fd6..ee6d86eb9 100644 --- a/resources/scripts/components/elements/button/style.module.css +++ b/resources/scripts/components/elements/button/style.module.css @@ -1,73 +1,2 @@ -.button { - @apply inline-flex items-center justify-center px-4 py-2; - @apply rounded-xl text-sm font-bold transition-all duration-100; - @apply focus:ring-[3px] focus:ring-opacity-50 focus:ring-offset-2 focus:ring-offset-zinc-700; - - /* Sizing Controls */ - &.small { - @apply h-8 px-4 py-0 text-sm font-normal focus:ring-2; - } - - &.large { - @apply px-5 py-3; - } - - &.secondary { - @apply bg-white bg-opacity-10; - - &:disabled { - background: transparent !important; - } - } - - &:disabled { - @apply cursor-not-allowed; - } - - &.square { - @apply h-12 w-12 p-0; - - &.small { - @apply h-8 w-8; - } - } -} - -.primary { - @apply bg-brand text-blue-50; - @apply hover:bg-brand/70 focus:ring-brand/50 focus:ring-opacity-75 active:bg-brand; - - &.secondary { - @apply hover:bg-opacity-10 active:bg-brand; - } - - &:disabled { - @apply bg-brand/25; - } -} - -.text { - @apply bg-zinc-700 text-zinc-50; - @apply hover:bg-zinc-600 focus:ring-zinc-300 focus:ring-opacity-50 active:bg-zinc-600; - - &.secondary { - @apply hover:bg-zinc-500 active:bg-zinc-500; - } - - &:disabled { - @apply bg-zinc-500/75 text-zinc-200/75; - } -} - -.danger { - @apply bg-red-600 text-zinc-50; - @apply hover:bg-red-500 focus:ring-red-400 focus:ring-opacity-75 active:bg-red-500; - - &.secondary { - @apply hover:bg-red-600 active:bg-red-600; - } - - &:disabled { - @apply bg-red-600/75 text-red-50/75; - } -} +/* @import "../../../../scripts/assets/tailwind.css'; */ +@import '../../../assets/tailwind.css' \ No newline at end of file diff --git a/resources/scripts/components/elements/button/types.ts b/resources/scripts/components/elements/button/types.ts index 273ac3b67..e86ec5e26 100644 --- a/resources/scripts/components/elements/button/types.ts +++ b/resources/scripts/components/elements/button/types.ts @@ -1,15 +1,15 @@ -enum Shape { +export enum Shape { Default, IconSquare, } -enum Size { +export enum Size { Default, Small, Large, } -enum Variant { +export enum Variant { Primary, Secondary, } diff --git a/resources/scripts/components/elements/commandk/CmdK.tsx b/resources/scripts/components/elements/commandk/CmdK.tsx index a1601ad55..0b60b6fcb 100644 --- a/resources/scripts/components/elements/commandk/CmdK.tsx +++ b/resources/scripts/components/elements/commandk/CmdK.tsx @@ -7,12 +7,18 @@ import Can from '@/components/elements/Can'; import { ServerContext } from '@/state/server'; +import ModrinthLogo from '../ModrinthLogo'; +import HugeIconsClock from '../hugeicons/Clock'; import HugeIconsCloudUp from '../hugeicons/CloudUp'; import HugeIconsConnections from '../hugeicons/Connections'; +import HugeIconsConsole from '../hugeicons/Console'; +import HugeIconsController from '../hugeicons/Controller'; import HugeIconsDashboardSettings from '../hugeicons/DashboardSettings'; import HugeIconsDatabase from '../hugeicons/Database'; import HugeIconsFolder from '../hugeicons/Folder'; import HugeIconsHome from '../hugeicons/Home'; +import HugeIconsPencil from '../hugeicons/Pencil'; +import HugeIconsPeople from '../hugeicons/People'; import HugeIconsZap from '../hugeicons/Zap'; const CommandMenu = () => { @@ -89,12 +95,48 @@ const CommandMenu = () => { Networking + + cmdkNavigate('/users')}> + + Users + + + + cmdkNavigate('/startup')}> + + Startup + + + + cmdkNavigate('/schedules')}> + + Schedules + + cmdkNavigate('/settings')}> Settings + + cmdkNavigate('/activity')}> + + Activity + + + + cmdkNavigate('/mods')}> + + Mods/Plugins + + + + cmdkNavigate('/shell')}> + + Software + + diff --git a/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx b/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx index 4c8b4f875..efc5d3ca9 100644 --- a/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx +++ b/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx @@ -1,26 +1,36 @@ import { useTranslation } from 'react-i18next'; -import { Button } from '@/components/elements/button/index'; +import ActionButton from '@/components/elements/ActionButton'; +import Spinner from '@/components/elements/Spinner'; import { Dialog, RenderDialogProps } from './'; type ConfirmationProps = Omit & { children: React.ReactNode; - confirm?: string | undefined; + confirm?: string; + loading?: boolean; onConfirmed: (e: React.MouseEvent) => void; }; -export default ({ confirm, children, onConfirmed, ...props }: ConfirmationProps) => { +const ConfirmationDialog = ({ children, confirm, onConfirmed, loading, ...props }: ConfirmationProps) => { const { t } = useTranslation(); - const confirmText = confirm || t('ok'); return ( {typeof children !== 'string' && children} - {t('cancel')} - {confirmText} + + {t('cancel')} + + +
+ {loading && } + {confirm ?? t('ok')} +
+
); }; + +export default ConfirmationDialog; diff --git a/resources/scripts/components/elements/dialog/Dialog.tsx b/resources/scripts/components/elements/dialog/Dialog.tsx index 46d83a00b..2f8f9faca 100644 --- a/resources/scripts/components/elements/dialog/Dialog.tsx +++ b/resources/scripts/components/elements/dialog/Dialog.tsx @@ -1,5 +1,5 @@ import { Dialog as HDialog } from '@headlessui/react'; -import { AnimatePresence, motion } from 'framer-motion'; +import { AnimatePresence, motion } from 'motion/react'; import { useRef, useState } from 'react'; import HugeIconsX from '../hugeicons/X'; @@ -31,7 +31,7 @@ const variants = { }, }; -export default ({ +const Dialog = ({ open, title, description, @@ -77,12 +77,12 @@ export default ({ background: 'radial-gradient(50% 50% at 50% 50%, rgba(0, 0, 0, 0.42) 0%, rgba(0, 0, 0, 0.94) 100%)', }} - className={'fixed inset-0 backdrop-blur-sm z-[9997]'} + className={'fixed inset-0 backdrop-blur-xs z-9997'} /> -
+
@@ -116,7 +116,7 @@ export default ({ {/* Keep this below the other buttons so that it isn't the default focus if they're present. */} {!hideCloseIcon && (
-
@@ -130,3 +130,5 @@ export default ({ ); }; + +export default Dialog; diff --git a/resources/scripts/components/elements/dialog/style.module.css b/resources/scripts/components/elements/dialog/style.module.css index 5e91b9914..7b4462721 100644 --- a/resources/scripts/components/elements/dialog/style.module.css +++ b/resources/scripts/components/elements/dialog/style.module.css @@ -1,22 +1,24 @@ -.container { +@import '../../../assets/tailwind.css'; + +.dialogContainer { @apply flex min-h-full items-center justify-center p-4 text-center; } .panel { - @apply relative border-[1px] border-[#ffffff07] rounded-2xl max-w-xl w-full mx-auto text-left shadow-2xl backdrop-blur-3xl; + @apply relative mx-auto w-full max-w-xl rounded-2xl border-[1px] border-[#ffffff07] text-left shadow-2xl backdrop-blur-3xl; background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), rgba(255, 255, 255, 0.12); } .title { - @apply text-2xl tracking-tight font-extrabold mb-2 pr-4; + @apply mb-2 pr-4 text-2xl font-extrabold tracking-tight; } .close_icon { - @apply w-5 h-5 group-hover:rotate-90 transition-transform duration-100; + @apply h-5 w-5 transition-transform duration-100 group-hover:rotate-90; } .dialog_icon { - @apply flex items-center justify-center w-10 h-10 rounded-full mr-4; + @apply mr-4 flex h-10 w-10 items-center justify-center rounded-full; &.danger { @apply bg-red-500 text-red-50; diff --git a/resources/scripts/components/elements/hugeicons/Alert.tsx b/resources/scripts/components/elements/hugeicons/Alert.tsx index efff148fe..339876cc9 100644 --- a/resources/scripts/components/elements/hugeicons/Alert.tsx +++ b/resources/scripts/components/elements/hugeicons/Alert.tsx @@ -14,7 +14,7 @@ const HugeIconsAlert = (props: HugeIconProps) => { fillRule='evenodd' clipRule='evenodd' d='M10.362 2.015C11.4264 1.66167 12.5736 1.66167 13.638 2.015C14.6981 2.36687 15.5304 3.20141 16.3665 4.37395C17.1999 5.5426 18.1208 7.17206 19.3078 9.2725L19.3544 9.35496C20.5417 11.4557 21.4625 13.0851 22.0364 14.4065C22.613 15.7343 22.9002 16.8807 22.6711 17.9821C22.4403 19.0911 21.8714 20.0995 21.0428 20.8617C20.2162 21.622 19.0907 21.9428 17.6736 22.0968C16.2645 22.25 14.4212 22.25 12.0488 22.25H11.9513C9.57882 22.25 7.73554 22.25 6.32642 22.0968C4.90927 21.9428 3.78379 21.622 2.95722 20.8617C2.12862 20.0995 1.55968 19.0911 1.32895 17.9821C1.0998 16.8807 1.387 15.7343 1.96365 14.4065C2.53752 13.0851 3.45835 11.4557 4.64558 9.35495L4.69218 9.2725L4.69218 9.27249C5.87921 7.17206 6.80008 5.5426 7.63347 4.37395C8.46963 3.20141 9.30194 2.36687 10.362 2.015ZM11 17C11 16.4477 11.4457 16 11.9955 16H12.0045C12.5543 16 13 16.4477 13 17C13 17.5523 12.5543 18 12.0045 18H11.9955C11.4457 18 11 17.5523 11 17ZM11 13C11 13.5523 11.4477 14 12 14C12.5523 14 13 13.5523 13 13V9C13 8.44772 12.5523 8 12 8C11.4477 8 11 8.44772 11 9V13Z' - fill={props.fill} + fill={props.fill || 'currentColor'} /> ); diff --git a/resources/scripts/components/elements/hugeicons/Calendar.tsx b/resources/scripts/components/elements/hugeicons/Calendar.tsx new file mode 100644 index 000000000..3576f5730 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Calendar.tsx @@ -0,0 +1,29 @@ +import { HugeIconProps } from './props'; + +const HugeIconsCalendar = (props: HugeIconProps) => { + return ( + + + + + ); +}; + +export default HugeIconsCalendar; diff --git a/resources/scripts/components/elements/hugeicons/Check.tsx b/resources/scripts/components/elements/hugeicons/Check.tsx new file mode 100644 index 000000000..81b7c9a65 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Check.tsx @@ -0,0 +1,21 @@ +import { HugeIconProps } from './props'; + +const HugeIconsCheck = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsCheck; diff --git a/resources/scripts/components/elements/hugeicons/ChevronDown.tsx b/resources/scripts/components/elements/hugeicons/ChevronDown.tsx new file mode 100644 index 000000000..5453d392f --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/ChevronDown.tsx @@ -0,0 +1,21 @@ +import { HugeIconProps } from './props'; + +const HugeIconsChevronDown = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsChevronDown; diff --git a/resources/scripts/components/elements/hugeicons/ChevronLeft.tsx b/resources/scripts/components/elements/hugeicons/ChevronLeft.tsx new file mode 100644 index 000000000..cd9f03bd8 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/ChevronLeft.tsx @@ -0,0 +1,21 @@ +import { HugeIconProps } from './props'; + +const HugeIconsChevronLeft = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsChevronLeft; diff --git a/resources/scripts/components/elements/hugeicons/ChevronRight.tsx b/resources/scripts/components/elements/hugeicons/ChevronRight.tsx new file mode 100644 index 000000000..482d41bdc --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/ChevronRight.tsx @@ -0,0 +1,21 @@ +import { HugeIconProps } from './props'; + +const HugeIconsChevronRight = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsChevronRight; diff --git a/resources/scripts/components/elements/hugeicons/ChevronUp.tsx b/resources/scripts/components/elements/hugeicons/ChevronUp.tsx new file mode 100644 index 000000000..de302175a --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/ChevronUp.tsx @@ -0,0 +1,21 @@ +import { HugeIconProps } from './props'; + +const HugeIconsChevronUp = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsChevronUp; diff --git a/resources/scripts/components/elements/hugeicons/CloudUp.tsx b/resources/scripts/components/elements/hugeicons/CloudUp.tsx index c53343bd9..5d5e6ebb8 100644 --- a/resources/scripts/components/elements/hugeicons/CloudUp.tsx +++ b/resources/scripts/components/elements/hugeicons/CloudUp.tsx @@ -14,11 +14,11 @@ const HugeIconsCloudUp = (props: HugeIconProps) => { fillRule='evenodd' clipRule='evenodd' d='M13.0059 21.25C13.0059 21.8023 12.5581 22.25 12.0059 22.25C11.4536 22.25 11.0059 21.8023 11.0059 21.25L11.0059 16.75L10.4116 16.75C10.236 16.7501 10.0203 16.7503 9.84387 16.7282L9.84053 16.7278C9.71408 16.712 9.13804 16.6402 8.86368 16.0746C8.58872 15.5077 8.89065 15.0076 8.95597 14.8994L8.95841 14.8954C9.05062 14.7424 9.18477 14.5715 9.29511 14.4309L9.31885 14.4007C9.61348 14.0248 9.99545 13.5406 10.3759 13.1496C10.5657 12.9545 10.783 12.7533 11.0139 12.5944C11.2191 12.4532 11.5693 12.25 12 12.25C12.4307 12.25 12.7809 12.4532 12.9861 12.5944C13.217 12.7533 13.4343 12.9545 13.6241 13.1496C14.0046 13.5406 14.3865 14.0248 14.6812 14.4007L14.7049 14.4309C14.8152 14.5715 14.9494 14.7423 15.0416 14.8954L15.044 14.8994C15.1093 15.0076 15.4113 15.5078 15.1363 16.0746C14.862 16.6402 14.2859 16.712 14.1595 16.7278L14.1561 16.7282C13.9797 16.7503 13.764 16.7501 13.5884 16.75L13.0059 16.75L13.0059 21.25Z' - fill={props.fill} + fill={props.fill || 'currentColor'} /> ); diff --git a/resources/scripts/components/elements/hugeicons/Code.tsx b/resources/scripts/components/elements/hugeicons/Code.tsx new file mode 100644 index 000000000..470c571aa --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Code.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsCode = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsCode; diff --git a/resources/scripts/components/elements/hugeicons/Copy.tsx b/resources/scripts/components/elements/hugeicons/Copy.tsx index 649b2a39d..1e8588e16 100644 --- a/resources/scripts/components/elements/hugeicons/Copy.tsx +++ b/resources/scripts/components/elements/hugeicons/Copy.tsx @@ -12,11 +12,11 @@ const HugeIconsCopy = (props: HugeIconProps) => { > ); diff --git a/resources/scripts/components/elements/hugeicons/Crown.tsx b/resources/scripts/components/elements/hugeicons/Crown.tsx new file mode 100644 index 000000000..2aa31a47b --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Crown.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsCrown = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsCrown; diff --git a/resources/scripts/components/elements/hugeicons/DotFilled.tsx b/resources/scripts/components/elements/hugeicons/DotFilled.tsx new file mode 100644 index 000000000..6e242ce81 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/DotFilled.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsDotFilled = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsDotFilled; diff --git a/resources/scripts/components/elements/hugeicons/Download.tsx b/resources/scripts/components/elements/hugeicons/Download.tsx index ae52bea9d..47738a60a 100644 --- a/resources/scripts/components/elements/hugeicons/Download.tsx +++ b/resources/scripts/components/elements/hugeicons/Download.tsx @@ -3,17 +3,24 @@ import { HugeIconProps } from './props'; // million-ignore const HugeIconsDownload = (props: HugeIconProps) => { return ( - + { + return ( + + + + ); +}; + +export default HugeIconsEye; diff --git a/resources/scripts/components/elements/hugeicons/EyeSlash.tsx b/resources/scripts/components/elements/hugeicons/EyeSlash.tsx new file mode 100644 index 000000000..5be15a778 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/EyeSlash.tsx @@ -0,0 +1,29 @@ +import { HugeIconProps } from './props'; + +const HugeIconsEyeSlash = (props: HugeIconProps) => { + return ( + + + + + ); +}; + +export default HugeIconsEyeSlash; diff --git a/resources/scripts/components/elements/hugeicons/File.tsx b/resources/scripts/components/elements/hugeicons/File.tsx new file mode 100644 index 000000000..3f5ad183a --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/File.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsFile = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsFile; diff --git a/resources/scripts/components/elements/hugeicons/Filter.tsx b/resources/scripts/components/elements/hugeicons/Filter.tsx new file mode 100644 index 000000000..521820689 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Filter.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsFilter = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsFilter; diff --git a/resources/scripts/components/elements/hugeicons/History.tsx b/resources/scripts/components/elements/hugeicons/History.tsx new file mode 100644 index 000000000..d2da784ba --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/History.tsx @@ -0,0 +1,29 @@ +import { HugeIconProps } from './props'; + +const HugeIconsHistory = (props: HugeIconProps) => { + return ( + + + + + ); +}; + +export default HugeIconsHistory; diff --git a/resources/scripts/components/elements/hugeicons/Key.tsx b/resources/scripts/components/elements/hugeicons/Key.tsx new file mode 100644 index 000000000..357c9e507 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Key.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsKey = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsKey; diff --git a/resources/scripts/components/elements/hugeicons/Link.tsx b/resources/scripts/components/elements/hugeicons/Link.tsx index 1a1a6456e..1afec3c88 100644 --- a/resources/scripts/components/elements/hugeicons/Link.tsx +++ b/resources/scripts/components/elements/hugeicons/Link.tsx @@ -5,7 +5,7 @@ const HugeIconsLink = (props: HugeIconProps) => { { + return ( + + + + ); +}; + +export default HugeIconsMenu; diff --git a/resources/scripts/components/elements/hugeicons/MoreHorizontal.tsx b/resources/scripts/components/elements/hugeicons/MoreHorizontal.tsx new file mode 100644 index 000000000..c738c867e --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/MoreHorizontal.tsx @@ -0,0 +1,35 @@ +import { HugeIconProps } from './props'; + +const HugeIconsMoreHorizontal = (props: HugeIconProps) => { + return ( + + + + + + ); +}; + +export default HugeIconsMoreHorizontal; diff --git a/resources/scripts/components/elements/hugeicons/NetworkAntenna.tsx b/resources/scripts/components/elements/hugeicons/NetworkAntenna.tsx new file mode 100644 index 000000000..11e9be57e --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/NetworkAntenna.tsx @@ -0,0 +1,35 @@ +import { HugeIconProps } from './props'; + +const HugeIconsNetworkAntenna = (props: HugeIconProps) => { + return ( + + + + + + ); +}; + +export default HugeIconsNetworkAntenna; diff --git a/resources/scripts/components/elements/hugeicons/Plus.tsx b/resources/scripts/components/elements/hugeicons/Plus.tsx new file mode 100644 index 000000000..92c3e2f65 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Plus.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsPlus = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsPlus; diff --git a/resources/scripts/components/elements/hugeicons/Power.tsx b/resources/scripts/components/elements/hugeicons/Power.tsx new file mode 100644 index 000000000..7a72c01f2 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Power.tsx @@ -0,0 +1,25 @@ +import { HugeIconProps } from './props'; + +const HugeIconsPower = (props: HugeIconProps) => { + return ( + + + + + ); +}; + +export default HugeIconsPower; diff --git a/resources/scripts/components/elements/hugeicons/Question.tsx b/resources/scripts/components/elements/hugeicons/Question.tsx new file mode 100644 index 000000000..6aa2bcaf8 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Question.tsx @@ -0,0 +1,21 @@ +import { HugeIconProps } from './props'; + +const HugeIconsQuestion = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsQuestion; diff --git a/resources/scripts/components/elements/hugeicons/Refresh.tsx b/resources/scripts/components/elements/hugeicons/Refresh.tsx new file mode 100644 index 000000000..e165d2f36 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Refresh.tsx @@ -0,0 +1,27 @@ +import { HugeIconProps } from './props'; + +const HugeIconsRefresh = (props: HugeIconProps) => { + return ( + + + + + ); +}; + +export default HugeIconsRefresh; diff --git a/resources/scripts/components/elements/hugeicons/Search.tsx b/resources/scripts/components/elements/hugeicons/Search.tsx new file mode 100644 index 000000000..b3ac4c42e --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Search.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsSearch = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsSearch; diff --git a/resources/scripts/components/elements/hugeicons/Server.tsx b/resources/scripts/components/elements/hugeicons/Server.tsx new file mode 100644 index 000000000..8358e7d07 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Server.tsx @@ -0,0 +1,29 @@ +import { HugeIconProps } from './props'; + +const HugeIconsServer = (props: HugeIconProps) => { + return ( + + + + + ); +}; + +export default HugeIconsServer; diff --git a/resources/scripts/components/elements/hugeicons/Settings.tsx b/resources/scripts/components/elements/hugeicons/Settings.tsx new file mode 100644 index 000000000..a7e728759 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Settings.tsx @@ -0,0 +1,23 @@ +import { HugeIconProps } from './props'; + +const HugeIconsSettings = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsSettings; diff --git a/resources/scripts/components/elements/hugeicons/Shield.tsx b/resources/scripts/components/elements/hugeicons/Shield.tsx new file mode 100644 index 000000000..b8dea4ef2 --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Shield.tsx @@ -0,0 +1,21 @@ +import { HugeIconProps } from './props'; + +const HugeIconsShield = (props: HugeIconProps) => { + return ( + + + + ); +}; + +export default HugeIconsShield; diff --git a/resources/scripts/components/elements/hugeicons/Trash.tsx b/resources/scripts/components/elements/hugeicons/Trash.tsx new file mode 100644 index 000000000..26413836f --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/Trash.tsx @@ -0,0 +1,29 @@ +import { HugeIconProps } from './props'; + +const HugeIconsTrash = (props: HugeIconProps) => { + return ( + + + + + ); +}; + +export default HugeIconsTrash; diff --git a/resources/scripts/components/elements/hugeicons/User.tsx b/resources/scripts/components/elements/hugeicons/User.tsx new file mode 100644 index 000000000..efab9f59b --- /dev/null +++ b/resources/scripts/components/elements/hugeicons/User.tsx @@ -0,0 +1,31 @@ +import { HugeIconProps } from './props'; + +const HugeIconsUser = (props: HugeIconProps) => { + return ( + + + + + + ); +}; + +export default HugeIconsUser; diff --git a/resources/scripts/components/elements/hugeicons/props.ts b/resources/scripts/components/elements/hugeicons/props.ts index 202ff6d12..f419224a4 100644 --- a/resources/scripts/components/elements/hugeicons/props.ts +++ b/resources/scripts/components/elements/hugeicons/props.ts @@ -1,4 +1,4 @@ export interface HugeIconProps { - fill: string; + fill?: string; className?: string; } diff --git a/resources/scripts/components/elements/inputs/Checkbox.tsx b/resources/scripts/components/elements/inputs/Checkbox.tsx index 7fd497680..2f6296efc 100644 --- a/resources/scripts/components/elements/inputs/Checkbox.tsx +++ b/resources/scripts/components/elements/inputs/Checkbox.tsx @@ -30,4 +30,6 @@ const CheckBox = forwardRef(({ className, label, inputF
)); +CheckBox.displayName = 'CheckBox'; + export default CheckBox; diff --git a/resources/scripts/components/elements/inputs/InputField.tsx b/resources/scripts/components/elements/inputs/InputField.tsx index 87d8c6f87..977c28957 100644 --- a/resources/scripts/components/elements/inputs/InputField.tsx +++ b/resources/scripts/components/elements/inputs/InputField.tsx @@ -23,4 +23,6 @@ const Component = forwardRef(({ className, va const InputField = Object.assign(Component, { Variants: Variant }); +Component.displayName = 'InputField'; + export default InputField; diff --git a/resources/scripts/components/elements/inputs/styles.module.css b/resources/scripts/components/elements/inputs/styles.module.css index f7c2f40ca..442afc14d 100644 --- a/resources/scripts/components/elements/inputs/styles.module.css +++ b/resources/scripts/components/elements/inputs/styles.module.css @@ -1,3 +1,5 @@ +@import '../../../assets/tailwind.css'; + .checkbox_input { @apply h-4 w-4 rounded-sm border-zinc-500 bg-zinc-600 text-zinc-500; diff --git a/resources/scripts/components/elements/pages/PageList.tsx b/resources/scripts/components/elements/pages/PageList.tsx index 803f15d2b..118cd9e4b 100644 --- a/resources/scripts/components/elements/pages/PageList.tsx +++ b/resources/scripts/components/elements/pages/PageList.tsx @@ -24,7 +24,7 @@ const PageListItem = ({ className, children }: Props) => {
{children} diff --git a/resources/scripts/components/elements/table/PaginationFooter.tsx b/resources/scripts/components/elements/table/PaginationFooter.tsx index b7b1638f0..33ae401d9 100644 --- a/resources/scripts/components/elements/table/PaginationFooter.tsx +++ b/resources/scripts/components/elements/table/PaginationFooter.tsx @@ -1,13 +1,11 @@ import clsx from 'clsx'; -import { Button } from '@/components/elements/button/index'; -import ArrowLeft from '@/components/elements/hugeicons/ArrowLeft'; -import ArrowRight from '@/components/elements/hugeicons/ArrowRight'; +import ActionButton from '@/components/elements/ActionButton'; +import HugeIconsChevronLeft from '@/components/elements/hugeicons/ChevronLeft'; +import HugeIconsChevronRight from '@/components/elements/hugeicons/ChevronRight'; import { PaginationDataSet } from '@/api/http'; -// FIXME: add icons back - interface Props { className?: string; pagination: PaginationDataSet; @@ -34,13 +32,6 @@ const PaginationFooter = ({ pagination, className, onPageSelect }: Props) => { return null; } - const buttonProps = (page: number) => ({ - size: Button.Sizes.Small, - shape: Button.Shapes.IconSquare, - variant: Button.Variants.Secondary, - onClick: () => onPageSelect(page), - }); - return (

@@ -52,25 +43,54 @@ const PaginationFooter = ({ pagination, className, onPageSelect }: Props) => {

{pagination.totalPages > 1 && (
- - - + onPageSelect(current - 1)} + disabled={current <= 1} + className='w-8 h-8 p-0 flex items-center justify-center' + > + + {pages.previous.reverse().map((value) => ( - + onPageSelect(value)} + className='w-8 h-8 p-0 flex items-center justify-center' + > {value} - + ))} - + {pages.next.map((value) => ( - + onPageSelect(value)} + className='w-8 h-8 p-0 flex items-center justify-center' + > {value} - + ))} - - - + onPageSelect(current + 1)} + disabled={current >= total} + className='w-8 h-8 p-0 flex items-center justify-center' + > + +
)}
diff --git a/resources/scripts/components/elements/transitions/FadeTransition.tsx b/resources/scripts/components/elements/transitions/FadeTransition.tsx index dc59f0883..2ec0398a1 100644 --- a/resources/scripts/components/elements/transitions/FadeTransition.tsx +++ b/resources/scripts/components/elements/transitions/FadeTransition.tsx @@ -15,7 +15,7 @@ interface Props { function FadeTransition({ children, duration, ...props }: Props) { const [enterDuration, exitDuration] = Array.isArray(duration) ? duration - : !duration + : duration! ? ['duration-200', 'duration-100'] : [duration, duration]; diff --git a/resources/scripts/components/server/ConflictStateRenderer.tsx b/resources/scripts/components/server/ConflictStateRenderer.tsx index d3e140509..9f3b91c45 100644 --- a/resources/scripts/components/server/ConflictStateRenderer.tsx +++ b/resources/scripts/components/server/ConflictStateRenderer.tsx @@ -4,7 +4,7 @@ import { ServerContext } from '@/state/server'; import Spinner from '../elements/Spinner'; -export default () => { +const ConflictStateRenderer = () => { const status = ServerContext.useStoreState((state) => state.server.data?.status || null); const isTransferring = ServerContext.useStoreState((state) => state.server.data?.isTransferring || false); const isNodeUnderMaintenance = ServerContext.useStoreState( @@ -12,11 +12,11 @@ export default () => { ); return status === 'installing' || status === 'install_failed' || status === 'reinstall_failed' ? ( -
+
-
+
-
@@ -39,3 +39,5 @@ export default () => { /> ); }; + +export default ConflictStateRenderer; diff --git a/resources/scripts/components/server/ServerActivityLogContainer.tsx b/resources/scripts/components/server/ServerActivityLogContainer.tsx index dcb63fdf7..601ba207d 100644 --- a/resources/scripts/components/server/ServerActivityLogContainer.tsx +++ b/resources/scripts/components/server/ServerActivityLogContainer.tsx @@ -1,32 +1,151 @@ -import clsx from 'clsx'; -import { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; +import { useEffect, useMemo, useState } from 'react'; import FlashMessageRender from '@/components/FlashMessageRender'; -import ContentBox from '@/components/elements/ContentBox'; +import ActionButton from '@/components/elements/ActionButton'; +import { MainPageHeader } from '@/components/elements/MainPageHeader'; +import Select from '@/components/elements/Select'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; import Spinner from '@/components/elements/Spinner'; import ActivityLogEntry from '@/components/elements/activity/ActivityLogEntry'; -import { styles as btnStyles } from '@/components/elements/button/index'; +import HugeIconsDownload from '@/components/elements/hugeicons/Download'; +import HugeIconsFilter from '@/components/elements/hugeicons/Filter'; +import HugeIconsHistory from '@/components/elements/hugeicons/History'; +import HugeIconsSearch from '@/components/elements/hugeicons/Search'; +import HugeIconsX from '@/components/elements/hugeicons/X'; +import { Input } from '@/components/elements/inputs'; import PaginationFooter from '@/components/elements/table/PaginationFooter'; import { ActivityLogFilters } from '@/api/account/activity'; import { useActivityLogs } from '@/api/server/activity'; import { useFlashKey } from '@/plugins/useFlash'; -// FIXME: add icons back import useLocationHash from '@/plugins/useLocationHash'; -export default () => { +const ServerActivityLogContainer = () => { const { hash } = useLocationHash(); const { clearAndAddHttpError } = useFlashKey('server:activity'); const [filters, setFilters] = useState({ page: 1, sorts: { timestamp: -1 } }); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedEventType, setSelectedEventType] = useState(''); + const [showFilters, setShowFilters] = useState(false); + const [dateRange, setDateRange] = useState('all'); const { data, isValidating, error } = useActivityLogs(filters, { revalidateOnMount: true, revalidateOnFocus: false, }); + // Extract unique event types for filter dropdown + const eventTypes = useMemo(() => { + if (!data?.items) return []; + const types = [...new Set(data.items.map((item) => item.event))]; + return types.sort(); + }, [data?.items]); + + // Filter data based on search term and event type + const filteredData = useMemo(() => { + if (!data?.items) return data; + + let filtered = data.items; + + if (searchTerm) { + filtered = filtered.filter( + (item) => + item.event.toLowerCase().includes(searchTerm.toLowerCase()) || + item.ip?.toLowerCase().includes(searchTerm.toLowerCase()) || + item.relationships.actor?.username?.toLowerCase().includes(searchTerm.toLowerCase()) || + JSON.stringify(item.properties).toLowerCase().includes(searchTerm.toLowerCase()), + ); + } + + if (selectedEventType) { + filtered = filtered.filter((item) => item.event === selectedEventType); + } + + // Apply date range filtering + if (dateRange !== 'all') { + const now = new Date(); + const cutoff = new Date(); + + switch (dateRange) { + case '1h': + cutoff.setHours(now.getHours() - 1); + break; + case '24h': + cutoff.setDate(now.getDate() - 1); + break; + case '7d': + cutoff.setDate(now.getDate() - 7); + break; + case '30d': + cutoff.setDate(now.getDate() - 30); + break; + } + + filtered = filtered.filter((item) => new Date(item.timestamp) >= cutoff); + } + + return { ...data, items: filtered }; + }, [data, searchTerm, selectedEventType, dateRange]); + + const exportLogs = () => { + if (!filteredData?.items) return; + + const csvContent = [ + ['Timestamp', 'Event', 'Actor', 'IP Address', 'Properties'].join(','), + ...filteredData.items.map((item) => + [ + new Date(item.timestamp).toISOString(), + item.event, + item.relationships.actor?.username || 'System', + item.ip || '', + JSON.stringify(item.properties).replace(/"/g, '""'), + ] + .map((field) => `"${field}"`) + .join(','), + ), + ].join('\n'); + + const blob = new Blob([csvContent], { type: 'text/csv' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `server-activity-log-${new Date().toISOString().split('T')[0]}.csv`; + a.click(); + window.URL.revokeObjectURL(url); + }; + + const clearAllFilters = () => { + setFilters((value) => ({ ...value, filters: {} })); + setSearchTerm(''); + setSelectedEventType(''); + setDateRange('all'); + }; + + const hasActiveFilters = + filters.filters?.event || filters.filters?.ip || searchTerm || selectedEventType || dateRange !== 'all'; + + // Keyboard shortcuts + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.ctrlKey || e.metaKey) { + switch (e.key) { + case 'f': + e.preventDefault(); + setShowFilters(!showFilters); + break; + case 'e': + e.preventDefault(); + exportLogs(); + break; + } + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [showFilters]); + useEffect(() => { setFilters((value) => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); }, [hash]); @@ -37,40 +156,219 @@ export default () => { return ( - - - {(filters.filters?.event || filters.filters?.ip) && ( -
- setFilters((value) => ({ ...value, filters: {} }))} - > - Clear Filters - {/* FIXME: X icon */} - +
+ + +
+ + setShowFilters(!showFilters)} + className='flex items-center gap-2' + title='Toggle Filters (Ctrl+F)' + > + + Filters + {hasActiveFilters && } + + + + Export + +
+ } + > +

+ Monitor all server activity and track user actions. Filter events, search for specific + activities, and export logs for audit purposes. +

+ +
+ + {showFilters && ( +
+
+
+
+ +
+

Filters

+
+ +
+
+ +
+ + setSearchTerm(e.target.value)} + style={{ paddingLeft: '2.5rem' }} + /> +
+
+ +
+ + +
+ +
+ + +
+ +
+ {hasActiveFilters && ( + + + Clear All Filters + + )} +
+
+
)} - {!data && isValidating ? ( - - ) : !data?.items.length ? ( -

No activity logs available for this server.

- ) : ( -
- {data?.items.map((activity) => ( - - - - ))} + +
+
+
+
+ +
+

Events

+ {filteredData?.items && ( + + ({filteredData.items.length} {filteredData.items.length === 1 ? 'event' : 'events'}) + + )} +
+ + {!data && isValidating ? ( + + ) : !filteredData?.items?.length ? ( +
+ +

+ {hasActiveFilters ? 'No Matching Activity' : 'No Server Activity Yet'} +

+

+ {hasActiveFilters + ? "Try adjusting your filters or search terms to find the activity you're looking for." + : 'Server activity logs will appear here as you manage your server. Start your server or perform actions to see them here.'} +

+ {hasActiveFilters && ( +
+ + Clear All Filters + + setShowFilters(true)}> + Adjust Filters + +
+ )} +
+ ) : ( +
+ {filteredData.items.map((activity) => ( + + + + ))} +
+ )} + + {data && ( +
+ setFilters((value) => ({ ...value, page }))} + /> +
+ )}
- )} - {data && ( - setFilters((value) => ({ ...value, page }))} - /> - )} - +
+
); }; + +export default ServerActivityLogContainer; diff --git a/resources/scripts/components/server/UptimeDuration.tsx b/resources/scripts/components/server/UptimeDuration.tsx index e87700a61..337c897d8 100644 --- a/resources/scripts/components/server/UptimeDuration.tsx +++ b/resources/scripts/components/server/UptimeDuration.tsx @@ -1,4 +1,4 @@ -export default ({ uptime }: { uptime: number }) => { +const UptimeDuration = ({ uptime }: { uptime: number }) => { const days = Math.floor(uptime / (24 * 60 * 60)); const hours = Math.floor((Math.floor(uptime) / 60 / 60) % 24); const remainder = Math.floor(uptime - hours * 60 * 60); @@ -19,3 +19,5 @@ export default ({ uptime }: { uptime: number }) => { ); }; + +export default UptimeDuration; diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index cb0e9d2c6..c79f1f3f9 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -89,12 +89,12 @@ function WebsocketHandler() { }; useEffect(() => { - connected && setError(''); + if (connected) setError(''); }, [connected]); useEffect(() => { return () => { - instance && instance.close(); + if (instance) instance.close(); }; }, [instance]); @@ -111,7 +111,7 @@ function WebsocketHandler() { return error ? (
{error === 'connecting' ? ( <> diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index 06354854a..aadf97d47 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -1,27 +1,135 @@ +import { Form, Formik, Field as FormikField, FormikHelpers, useFormikContext } from 'formik'; import { useContext, useEffect, useState } from 'react'; +import { boolean, object, string } from 'yup'; import FlashMessageRender from '@/components/FlashMessageRender'; +import ActionButton from '@/components/elements/ActionButton'; import Can from '@/components/elements/Can'; +import Field from '@/components/elements/Field'; +import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; +import FormikSwitchV2 from '@/components/elements/FormikSwitchV2'; +import { Textarea } from '@/components/elements/Input'; import { MainPageHeader } from '@/components/elements/MainPageHeader'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import Pagination from '@/components/elements/Pagination'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; +import Spinner from '@/components/elements/Spinner'; import { PageListContainer } from '@/components/elements/pages/PageList'; import BackupRow from '@/components/server/backups/BackupRow'; -import CreateBackupButton from '@/components/server/backups/CreateBackupButton'; +import createServerBackup from '@/api/server/backups/createServerBackup'; import getServerBackups, { Context as ServerBackupContext } from '@/api/swr/getServerBackups'; import { ServerContext } from '@/state/server'; import useFlash from '@/plugins/useFlash'; +interface BackupValues { + name: string; + ignored: string; + isLocked: boolean; +} + +const ModalContent = ({ ...props }: RequiredModalProps) => { + const { isSubmitting } = useFormikContext(); + + return ( + +
+ + +
+ + + +
+ +
+ +
+
+
+ + {isSubmitting && } + {isSubmitting ? 'Creating backup...' : 'Start backup'} + +
+ +
+ ); +}; + const BackupContainer = () => { const { page, setPage } = useContext(ServerBackupContext); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { data: backups, error, isValidating } = getServerBackups(); + const { data: backups, error, isValidating, mutate } = getServerBackups(); + const [createModalVisible, setCreateModalVisible] = useState(false); + const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); const backupLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.backups); + const hasBackupsInProgress = backups?.items.some((backup) => backup.completedAt === null) || false; + + useEffect(() => { + let interval: NodeJS.Timeout; + + if (hasBackupsInProgress) { + interval = setInterval(() => { + mutate(); + }, 2000); + } + + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [hasBackupsInProgress, mutate]); + + useEffect(() => { + clearFlashes('backups:create'); + }, [createModalVisible]); + + const submitBackup = (values: BackupValues, { setSubmitting }: FormikHelpers) => { + clearFlashes('backups:create'); + createServerBackup(uuid, values) + .then(async (backup) => { + await mutate( + (data) => ({ ...data!, items: data!.items.concat(backup), backupCount: data!.backupCount + 1 }), + false, + ); + setSubmitting(false); + setCreateModalVisible(false); + }) + .catch((error) => { + clearAndAddHttpError({ key: 'backups:create', error }); + setSubmitting(false); + }); + }; + useEffect(() => { if (!error) { clearFlashes('backups'); @@ -35,38 +143,87 @@ const BackupContainer = () => { if (!backups || (error && isValidating)) { return ( -

Backups

+ + +

+ Create and manage server backups to protect your data. Schedule automated backups, download + existing ones, and restore when needed. +

+
+
+
+
); } return ( - - -
- {backupLimit > 0 && backups.backupCount > 0 && ( -

- {backups.backupCount} of {backupLimit} backups -

- )} - {backupLimit > 0 && backupLimit > backups.backupCount && } -
-
-
+ +
+ {backupLimit > 0 && ( +

+ {backups.backupCount} of {backupLimit} backups +

+ )} + {backupLimit > 0 && backupLimit > backups.backupCount && ( + setCreateModalVisible(true)}> + New Backup + + )} +
+ + } + > +

+ Create and manage server backups to protect your data. Schedule automated backups, download existing + ones, and restore when needed. +

+
+ + {createModalVisible && ( + + setCreateModalVisible(false)} /> + + )} + {({ items }) => !items.length ? ( - // Don't show any error messages if the server has no backups and the user cannot - // create additional ones for the server. - !backupLimit ? null : ( -

- {page > 1 - ? "Looks like we've run out of backups to show you, try going back a page." - : 'Your server does not have any backups.'} -

- ) +
+
+
+ + + +
+

+ {backupLimit > 0 ? 'No backups found' : 'Backups unavailable'} +

+

+ {backupLimit > 0 + ? 'Your server does not have any backups. Create one to get started.' + : 'Backups cannot be created for this server.'} +

+
+
) : ( {items.map((backup) => ( @@ -76,14 +233,11 @@ const BackupContainer = () => { ) }
- {backupLimit === 0 && ( -

Backups cannot be created for this server.

- )}
); }; -export default () => { +const BackupContainerWrapper = () => { const [page, setPage] = useState(1); return ( @@ -91,3 +245,5 @@ export default () => { ); }; + +export default BackupContainerWrapper; diff --git a/resources/scripts/components/server/backups/BackupContextMenu.tsx b/resources/scripts/components/server/backups/BackupContextMenu.tsx index 502cdd1e9..d76f775ca 100644 --- a/resources/scripts/components/server/backups/BackupContextMenu.tsx +++ b/resources/scripts/components/server/backups/BackupContextMenu.tsx @@ -1,18 +1,31 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; +import ActionButton from '@/components/elements/ActionButton'; import Can from '@/components/elements/Can'; -import { ContextMenuContent, ContextMenuItem } from '@/components/elements/ContextMenu'; -import Input from '@/components/elements/Input'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/elements/DropdownMenu'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import { Dialog } from '@/components/elements/dialog'; +import HugeIconsAlert from '@/components/elements/hugeicons/Alert'; +import HugeIconsCloudUp from '@/components/elements/hugeicons/CloudUp'; import HugeIconsDelete from '@/components/elements/hugeicons/Delete'; import HugeIconsFileDownload from '@/components/elements/hugeicons/FileDownload'; import HugeIconsFileSecurity from '@/components/elements/hugeicons/FileSecurity'; +import HugeIconsPencil from '@/components/elements/hugeicons/Pencil'; +import HugeIconsHamburger from '@/components/elements/hugeicons/hamburger'; import http, { httpErrorToHuman } from '@/api/http'; -import { restoreServerBackup } from '@/api/server/backups'; -import deleteBackup from '@/api/server/backups/deleteBackup'; -import getBackupDownloadUrl from '@/api/server/backups/getBackupDownloadUrl'; +import { + deleteServerBackup, + getServerBackupDownloadUrl, + renameServerBackup, + restoreServerBackup, +} from '@/api/server/backups'; import { ServerBackup } from '@/api/server/types'; import getServerBackups from '@/api/swr/getServerBackups'; @@ -24,19 +37,20 @@ interface Props { backup: ServerBackup; } -export default ({ backup }: Props) => { +const BackupContextMenu = ({ backup }: Props) => { const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); const [modal, setModal] = useState(''); const [loading, setLoading] = useState(false); - const [truncate, setTruncate] = useState(false); + const [countdown, setCountdown] = useState(5); + const [newName, setNewName] = useState(backup.name); const { clearFlashes, clearAndAddHttpError } = useFlash(); const { mutate } = getServerBackups(); const doDownload = () => { setLoading(true); clearFlashes('backups'); - getBackupDownloadUrl(uuid, backup.uuid) + getServerBackupDownloadUrl(uuid, backup.uuid) .then((url) => { // @ts-expect-error this is valid window.location = url; @@ -51,7 +65,7 @@ export default ({ backup }: Props) => { const doDeletion = () => { setLoading(true); clearFlashes('backups'); - deleteBackup(uuid, backup.uuid) + deleteServerBackup(uuid, backup.uuid) .then( async () => await mutate( @@ -73,7 +87,7 @@ export default ({ backup }: Props) => { const doRestorationAction = () => { setLoading(true); clearFlashes('backups'); - restoreServerBackup(uuid, backup.uuid, truncate) + restoreServerBackup(uuid, backup.uuid) .then(() => setServerFromState((s) => ({ ...s, @@ -115,8 +129,94 @@ export default ({ backup }: Props) => { .then(() => setModal('')); }; + const doRename = () => { + setLoading(true); + clearFlashes('backups'); + renameServerBackup(uuid, backup.uuid, newName.trim()) + .then( + async () => + await mutate( + (data) => ({ + ...data!, + items: data!.items.map((b) => + b.uuid !== backup.uuid + ? b + : { + ...b, + name: newName.trim(), + }, + ), + }), + false, + ), + ) + .catch((error) => { + console.error(error); + clearAndAddHttpError({ key: 'backups', error }); + }) + .then(() => { + setLoading(false); + setModal(''); + }); + }; + + // Countdown effect for restore modal + useEffect(() => { + let interval: NodeJS.Timeout; + if (modal === 'restore' && countdown > 0) { + interval = setInterval(() => { + setCountdown((prev) => prev - 1); + }, 1000); + } + return () => { + if (interval) clearInterval(interval); + }; + }, [modal, countdown]); + + // Reset countdown when modal opens + useEffect(() => { + if (modal === 'restore') { + setCountdown(5); + } + }, [modal]); + + // Reset name when modal opens + useEffect(() => { + if (modal === 'rename') { + setNewName(backup.name); + } + }, [modal, backup.name]); + return ( <> + setModal('')} title='Rename Backup'> +
+
+ + setNewName(e.target.value)} + className='w-full px-3 py-2 bg-zinc-800 border border-zinc-600 rounded-lg text-zinc-100 placeholder-zinc-400 focus:border-blue-500 focus:ring-1 focus:ring-blue-500' + placeholder='Enter backup name...' + maxLength={191} + /> +
+
+ + + setModal('')} variant='secondary'> + Cancel + + + Rename Backup + + +
setModal('')} @@ -125,30 +225,41 @@ export default ({ backup }: Props) => { > This backup will no longer be protected from automated or accidental deletions. - setModal('')} - confirm={'Restore'} - title={`Restore "${backup.name}"`} - onConfirmed={() => doRestorationAction()} - > -

- Your server will be stopped. You will not be able to control the power state, access the file - manager, or create additional backups until completed. -

-

- -

-
+ setModal('')} title='Restore Backup'> +
+
+

"{backup.name}"

+

+ Your server will be stopped during the restoration process. You will not be able to control + the power state, access the file manager, or create additional backups until completed. +

+
+ +
+
+ +
+

+ Destructive Action - Complete Server Restore +

+

+ All current files and server configuration will be deleted and replaced with the + backup data. This action cannot be undone. +

+
+
+
+
+ + + setModal('')} variant='secondary'> + Cancel + + doRestorationAction()} variant='danger' disabled={countdown > 0}> + {countdown > 0 ? `Delete All & Restore (${countdown}s)` : 'Delete All & Restore Backup'} + + +
{ {backup.isSuccessful ? ( - - - - - Download Backup - - - - setModal('restore')}> - - Restore Backup - - - - <> - - + + + + + + + + + + + Download + + + + setModal('restore')} className='cursor-pointer'> + + Restore + + + + + setModal('rename')} className='cursor-pointer'> + + Rename + + + {backup.isLocked ? 'Unlock' : 'Lock'} - + {!backup.isLocked && ( - setModal('delete')}> - - Delete Backup - + <> + + setModal('delete')} + className='cursor-pointer text-red-400 focus:text-red-300' + > + + Delete + + )} - - - + + + ) : ( - + + Delete + )} ); }; + +export default BackupContextMenu; diff --git a/resources/scripts/components/server/backups/BackupRow.tsx b/resources/scripts/components/server/backups/BackupRow.tsx index ae6c6745b..7e666b16a 100644 --- a/resources/scripts/components/server/backups/BackupRow.tsx +++ b/resources/scripts/components/server/backups/BackupRow.tsx @@ -1,20 +1,17 @@ -import { faFile, faLock } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { format, formatDistanceToNow } from 'date-fns'; import Can from '@/components/elements/Can'; -import { ContextMenu, ContextMenuTrigger } from '@/components/elements/ContextMenu'; import Spinner from '@/components/elements/Spinner'; +import HugeIconsSquareLock from '@/components/elements/hugeicons/SquareLock'; +import HugeIconsStorage from '@/components/elements/hugeicons/Storage'; import { PageListItem } from '@/components/elements/pages/PageList'; import { SocketEvent } from '@/components/server/events'; import { bytesToString } from '@/lib/formatters'; import { ServerBackup } from '@/api/server/types'; -// import BackupContextMenu from '@/components/server/backups/BackupContextMenu'; import getServerBackups from '@/api/swr/getServerBackups'; -// import Can from '@/components/elements/Can'; import useWebsocketEvent from '@/plugins/useWebsocketEvent'; import BackupContextMenu from './BackupContextMenu'; @@ -23,94 +20,83 @@ interface Props { backup: ServerBackup; } -export default ({ backup }: Props) => { +const BackupRow = ({ backup }: Props) => { const { mutate } = getServerBackups(); - useWebsocketEvent(`${SocketEvent.BACKUP_COMPLETED}:${backup.uuid}` as SocketEvent, async (data) => { + useWebsocketEvent(`${SocketEvent.BACKUP_COMPLETED}:${backup.uuid}` as SocketEvent, async () => { try { - const parsed = JSON.parse(data); - - await mutate( - (data) => ({ - ...data!, - items: data!.items.map((b) => - b.uuid !== backup.uuid - ? b - : { - ...b, - isSuccessful: parsed.is_successful || true, - checksum: (parsed.checksum_type || '') + ':' + (parsed.checksum || ''), - bytes: parsed.file_size || 0, - completedAt: new Date(), - }, - ), - }), - false, - ); + // When backup completes, refresh the backup list from API to get accurate completion time + // This ensures we get the exact completion timestamp from the database, not the websocket receive time + await mutate(); } catch (e) { console.warn(e); } }); return ( - - - -
-
-
- {backup.completedAt === null ? ( - - ) : backup.isLocked ? ( - - ) : ( - - )} -
-
-
-
- {backup.completedAt !== null && !backup.isSuccessful && ( - - Failed - - )} -
-

{backup.name}

-
-
- {backup.checksum && ( -

- {backup.checksum} -

- )} -
-
-
-
+ +
+ {/* Status Icon */} +
+ {backup.completedAt === null ? ( + + ) : backup.isLocked ? ( + + ) : backup.isSuccessful ? ( + + ) : ( + + )} +
-
- {backup.completedAt !== null && backup.isSuccessful && ( - <> - {bytesToString(backup.bytes)} -

- + {/* Main Content */} +
+
+ {backup.completedAt !== null && !backup.isSuccessful && ( + + Failed + )} -

{backup.name} + - {formatDistanceToNow(backup.createdAt, { includeSeconds: true, addSuffix: true })} -

+ Locked +
+ {backup.checksum &&

{backup.checksum}

} +
+ {/* Size Info */} + {backup.completedAt !== null && backup.isSuccessful && ( +
+

Size

+

{bytesToString(backup.bytes)}

+
+ )} + + {/* Date Info */} +
+

Created

+

+ {formatDistanceToNow(backup.createdAt, { includeSeconds: true, addSuffix: true })} +

+
+ + {/* Actions Menu */} +
- {!backup.completedAt ? <> : } + {backup.completedAt ? : null} - - - +
+
+ ); }; + +export default BackupRow; diff --git a/resources/scripts/components/server/backups/CreateBackupButton.tsx b/resources/scripts/components/server/backups/CreateBackupButton.tsx deleted file mode 100644 index 211eba1cb..000000000 --- a/resources/scripts/components/server/backups/CreateBackupButton.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { Form, Formik, Field as FormikField, FormikHelpers, useFormikContext } from 'formik'; -import { useEffect, useState } from 'react'; -import { boolean, object, string } from 'yup'; - -import FlashMessageRender from '@/components/FlashMessageRender'; -import Can from '@/components/elements/Can'; -import Field from '@/components/elements/Field'; -import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; -import FormikSwitchV2 from '@/components/elements/FormikSwitchV2'; -import { Textarea } from '@/components/elements/Input'; -import Modal, { RequiredModalProps } from '@/components/elements/Modal'; -import { Button } from '@/components/elements/button/index'; - -import createServerBackup from '@/api/server/backups/createServerBackup'; -import getServerBackups from '@/api/swr/getServerBackups'; - -import { ServerContext } from '@/state/server'; - -import useFlash from '@/plugins/useFlash'; - -interface Values { - name: string; - ignored: string; - isLocked: boolean; - children?: React.ReactNode; -} - -const ModalContent = ({ ...props }: RequiredModalProps) => { - const { isSubmitting } = useFormikContext(); - - return ( - -
- - -
- - - -
- -
- -
-
-
- -
- -
- ); -}; - -export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const { clearFlashes, clearAndAddHttpError } = useFlash(); - const [visible, setVisible] = useState(false); - const { mutate } = getServerBackups(); - - useEffect(() => { - clearFlashes('backups:create'); - }, [visible]); - - const submit = (values: Values, { setSubmitting }: FormikHelpers) => { - clearFlashes('backups:create'); - createServerBackup(uuid, values) - .then(async (backup) => { - await mutate( - (data) => ({ ...data!, items: data!.items.concat(backup), backupCount: data!.backupCount + 1 }), - false, - ); - setVisible(false); - }) - .catch((error) => { - clearAndAddHttpError({ key: 'backups:create', error }); - setSubmitting(false); - }); - }; - - return ( - <> - {visible && ( - - setVisible(false)} /> - - )} - - - ); -}; diff --git a/resources/scripts/components/server/console/ChartBlock.tsx b/resources/scripts/components/server/console/ChartBlock.tsx index 877cbdcae..b10eecd4a 100644 --- a/resources/scripts/components/server/console/ChartBlock.tsx +++ b/resources/scripts/components/server/console/ChartBlock.tsx @@ -8,12 +8,15 @@ interface ChartBlockProps { children: React.ReactNode; } +// eslint-disable-next-line react/display-name export default ({ title, legend, children }: ChartBlockProps) => ( -
-
-

{title}

- {legend &&

{legend}

} +
+
+

+ {title} +

+ {legend &&
{legend}
}
-
{children}
+
{children}
); diff --git a/resources/scripts/components/server/console/Console.tsx b/resources/scripts/components/server/console/Console.tsx index 8a9d66e79..07e222d06 100644 --- a/resources/scripts/components/server/console/Console.tsx +++ b/resources/scripts/components/server/console/Console.tsx @@ -45,16 +45,22 @@ const terminalProps: ITerminalOptions = { disableStdin: true, cursorStyle: 'underline', allowTransparency: true, - fontSize: 12, - fontFamily: 'monospace, monospace', - // rows: 30, + fontSize: window.innerWidth < 640 ? 11 : 12, + fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace', theme: theme, }; -export default () => { - const TERMINAL_PRELUDE = '\u001b[1m\u001b[33mcontainer@pterodactyl~ \u001b[0m'; +const Console = () => { + const TERMINAL_PRELUDE = '\u001b[1m\u001b[33mcontainer@pyrodactyl~ \u001b[0m'; const ref = useRef(null); - const terminal = useMemo(() => new Terminal({ ...terminalProps, rows: 30 }), []); + const terminal = useMemo( + () => + new Terminal({ + ...terminalProps, + rows: window.innerWidth < 640 ? 20 : 25, + }), + [], + ); const fitAddon = new FitAddon(); const searchAddon = new SearchAddon(); const webLinksAddon = new WebLinksAddon(); @@ -109,7 +115,7 @@ export default () => { setHistory((prevHistory) => [command, ...prevHistory!].slice(0, 32)); setHistoryIndex(-1); - instance && instance.send('send command', command); + if (instance) instance.send('send command', command); e.currentTarget.value = ''; } }; @@ -145,6 +151,9 @@ export default () => { 'resize', debounce(() => { if (terminal.element) { + // Update font size based on window width + const newFontSize = window.innerWidth < 640 ? 11 : 12; + terminal.options.fontSize = newFontSize; fitAddon.fit(); } }, 100), @@ -193,35 +202,25 @@ export default () => { }, [connected, instance]); return ( -
-
+
+
-
-
-
+
+
+
{canSendCommands && ( -
+
)} @@ -229,3 +228,5 @@ export default () => {
); }; + +export default Console; diff --git a/resources/scripts/components/server/console/PowerButtons.tsx b/resources/scripts/components/server/console/PowerButtons.tsx index 0f4c3a41c..abc33fed9 100644 --- a/resources/scripts/components/server/console/PowerButtons.tsx +++ b/resources/scripts/components/server/console/PowerButtons.tsx @@ -11,7 +11,7 @@ interface PowerButtonProps { className?: string; } -export default ({ className }: PowerButtonProps) => { +const PowerButtons = ({ className }: PowerButtonProps) => { const [open, setOpen] = useState(false); const status = ServerContext.useStoreState((state) => state.status.value); const instance = ServerContext.useStoreState((state) => state.socket.instance); @@ -82,7 +82,7 @@ export default ({ className }: PowerButtonProps) => { opacity: 0.5, } } - className='px-8 py-3 border-[1px] border-[#ffffff12] rounded-l-full rounded-r-md text-sm font-bold shadow-md' + className='px-8 py-3 border-[1px] border-[#ffffff12] rounded-l-full rounded-r-md text-sm font-bold shadow-md cursor-pointer' disabled={status !== 'offline'} onClick={onButtonClick.bind(this, 'start')} > @@ -95,7 +95,7 @@ export default ({ className }: PowerButtonProps) => { background: 'radial-gradient(124.75% 124.75% at 50.01% -10.55%, rgb(36, 36, 36) 0%, rgb(20, 20, 20) 100%)', }} - className='px-8 py-3 border-[1px] border-[#ffffff12] rounded-none text-sm font-bold shadow-md' + className='px-8 py-3 border-[1px] border-[#ffffff12] rounded-none text-sm font-bold shadow-md cursor-pointer' disabled={!status} onClick={onButtonClick.bind(this, 'restart')} > @@ -117,7 +117,7 @@ export default ({ className }: PowerButtonProps) => { opacity: 1, } } - className='px-8 py-3 border-[1px] border-[#ffffff12] rounded-r-full rounded-l-md text-sm font-bold shadow-md transition-all' + className='px-8 py-3 border-[1px] border-[#ffffff12] rounded-r-full rounded-l-md text-sm font-bold shadow-md transition-all cursor-pointer' disabled={status === 'offline'} onClick={onButtonClick.bind(this, killable ? 'kill' : 'stop')} > @@ -127,3 +127,5 @@ export default ({ className }: PowerButtonProps) => {
); }; + +export default PowerButtons; diff --git a/resources/scripts/components/server/console/ServerConsoleContainer.tsx b/resources/scripts/components/server/console/ServerConsoleContainer.tsx index 88b79f2d7..00f2d27d6 100644 --- a/resources/scripts/components/server/console/ServerConsoleContainer.tsx +++ b/resources/scripts/components/server/console/ServerConsoleContainer.tsx @@ -11,13 +11,12 @@ import Console from '@/components/server/console/Console'; import PowerButtons from '@/components/server/console/PowerButtons'; import ServerDetailsBlock from '@/components/server/console/ServerDetailsBlock'; import StatGraphs from '@/components/server/console/StatGraphs'; +import { CrashAnalysisCard } from '@/components/server/features/MclogsFeature'; import { ServerContext } from '@/state/server'; import Features from '@feature/Features'; -import { StatusPill } from './StatusPill'; - export type PowerAction = 'start' | 'stop' | 'restart' | 'kill'; const ServerConsoleContainer = () => { @@ -30,34 +29,137 @@ const ServerConsoleContainer = () => { return ( -
+
{(isNodeUnderMaintenance || isInstalling || isTransferring) && ( - - {isNodeUnderMaintenance - ? 'The node of this server is currently under maintenance and all actions are unavailable.' - : isInstalling - ? 'This server is currently running its installation process and most actions are unavailable.' - : 'This server is currently being transferred to another node and all actions are unavailable.'} - +
+ + {isNodeUnderMaintenance + ? 'The node of this server is currently under maintenance and all actions are unavailable.' + : isInstalling + ? 'This server is currently running its installation process and most actions are unavailable.' + : 'This server is currently being transferred to another node and all actions are unavailable.'} + +
)} - }> - - - {description && ( -

- {description} -

- )} - - -
- - - + +
+ + +
+ } + /> +
+ + {description && ( +
+
+

{description}

+
+
+ )} + +
+
+
+ +
+
+ + {/* Crash Analysis Card - only shows if mclogs feature is enabled */} + {eggFeatures.map((v) => v.toLowerCase()).includes('mclogs') && ( +
+ +
+ )} + +
+
+ +
+
+ +
+
+
+ + + +
+
+
+ +
+ + + +
- - -
); diff --git a/resources/scripts/components/server/console/ServerDetailsBlock.tsx b/resources/scripts/components/server/console/ServerDetailsBlock.tsx index 547b4431e..7f9ed38e9 100644 --- a/resources/scripts/components/server/console/ServerDetailsBlock.tsx +++ b/resources/scripts/components/server/console/ServerDetailsBlock.tsx @@ -9,6 +9,7 @@ import { bytesToString, ip, mbToBytes } from '@/lib/formatters'; import { ServerContext } from '@/state/server'; import useWebsocketEvent from '@/plugins/useWebsocketEvent'; +import { getSubdomainInfo, SubdomainInfo } from '@/api/server/network/subdomain'; type Stats = Record<'memory' | 'cpu' | 'disk' | 'uptime' | 'rx' | 'tx', number>; @@ -25,17 +26,20 @@ type Stats = Record<'memory' | 'cpu' | 'disk' | 'uptime' | 'rx' | 'tx', number>; // return undefined; // }; -// @ts-ignore +// @ts-expect-error - Unused parameter in component definition // eslint-disable-next-line @typescript-eslint/no-unused-vars const Limit = ({ limit, children }: { limit: string | null; children: React.ReactNode }) => <>{children}; const ServerDetailsBlock = ({ className }: { className?: string }) => { const [stats, setStats] = useState({ memory: 0, cpu: 0, disk: 0, uptime: 0, tx: 0, rx: 0 }); + const [subdomainInfo, setSubdomainInfo] = useState(null); const status = ServerContext.useStoreState((state) => state.status.value); const connected = ServerContext.useStoreState((state) => state.socket.connected); const instance = ServerContext.useStoreState((state) => state.socket.instance); const limits = ServerContext.useStoreState((state) => state.server.data!.limits); + const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const serverAllocations = ServerContext.useStoreState((state) => state.server.data!.allocations); const textLimits = useMemo( () => ({ @@ -52,6 +56,28 @@ const ServerDetailsBlock = ({ className }: { className?: string }) => { return !match ? 'n/a' : `${match.alias || ip(match.ip)}:${match.port}`; }); + // Get display address (subdomain if available and active, otherwise IP) + const displayAddress = useMemo(() => { + if (subdomainInfo?.current_subdomain?.attributes?.is_active && subdomainInfo.current_subdomain.attributes.full_domain) { + return subdomainInfo.current_subdomain.attributes.full_domain; + } + return allocation; + }, [subdomainInfo, allocation, serverAllocations]); + + useEffect(() => { + const loadSubdomainInfo = async () => { + try { + const data = await getSubdomainInfo(uuid); + setSubdomainInfo(data); + } catch (error) { + // Silently fail - subdomain feature might not be available + setSubdomainInfo(null); + } + }; + + loadSubdomainInfo(); + }, [uuid]); + useEffect(() => { if (!connected || !instance) { return; @@ -79,27 +105,23 @@ const ServerDetailsBlock = ({ className }: { className?: string }) => { }); return ( -
+
- - {allocation} + + {displayAddress}
{
{
{ +const StatBlock = ({ title, copyOnClick, className, children }: StatBlockProps) => { return ( -
+
-

{title}

-
+

+ {title} +

+
{children}
@@ -25,3 +36,5 @@ export default ({ title, copyOnClick, className, children }: StatBlockProps) => ); }; + +export default StatBlock; diff --git a/resources/scripts/components/server/console/StatGraphs.tsx b/resources/scripts/components/server/console/StatGraphs.tsx index 201856fed..b6b219231 100644 --- a/resources/scripts/components/server/console/StatGraphs.tsx +++ b/resources/scripts/components/server/console/StatGraphs.tsx @@ -1,7 +1,9 @@ -// FIXME: add icons back +import * as Tooltip from '@radix-ui/react-tooltip'; import { useEffect, useRef } from 'react'; import { Line } from 'react-chartjs-2'; +import HugeIconsCloudUp from '@/components/elements/hugeicons/CloudUp'; +import HugeIconsDownload from '@/components/elements/hugeicons/Download'; import ChartBlock from '@/components/server/console/ChartBlock'; import { useChart, useChartTickLabel } from '@/components/server/console/chart'; import { SocketEvent } from '@/components/server/events'; @@ -13,7 +15,7 @@ import { ServerContext } from '@/state/server'; import useWebsocketEvent from '@/plugins/useWebsocketEvent'; -export default () => { +const StatGraphs = () => { const status = ServerContext.useStoreState((state) => state.status.value); const limits = ServerContext.useStoreState((state) => state.server.data!.limits); const previous = useRef>({ tx: -1, rx: -1 }); @@ -38,7 +40,6 @@ export default () => { ...opts, label: !index ? 'Network In' : 'Network Out', borderColor: !index ? '#facc15' : '#60a5fa', - // backgroundColor: hexToRgba(!index ? theme('colors.cyan.700') : theme('colors.yellow.700'), 0.5), backgroundColor: hexToRgba(!index ? '#facc15' : '#60a5fa', 0.09), }; }, @@ -70,7 +71,7 @@ export default () => { }); return ( - <> +
{ - {/* FIXME: add icons legend back */} - {/* FIXME: replace with radix tooltip */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - +
+ + +
+ +
+
+ + + Inbound + + + +
+ + + +
+ +
+
+ + + Outbound + + + +
+
} >
- +
); }; + +export default StatGraphs; diff --git a/resources/scripts/components/server/console/chart.ts b/resources/scripts/components/server/console/chart.ts index 4c70987ef..4216fbb47 100644 --- a/resources/scripts/components/server/console/chart.ts +++ b/resources/scripts/components/server/console/chart.ts @@ -66,7 +66,7 @@ const options: ChartOptions<'line'> = { }; function getOptions(opts?: DeepPartial> | undefined): ChartOptions<'line'> { - // @ts-ignore I'm not even going to try to tell you what this error means + // @ts-expect-error - deepmerge type compatibility issue with ChartOptions return deepmerge(options, opts || {}); } diff --git a/resources/scripts/components/server/console/style.module.css b/resources/scripts/components/server/console/style.module.css index f162f28b5..5f599efec 100644 --- a/resources/scripts/components/server/console/style.module.css +++ b/resources/scripts/components/server/console/style.module.css @@ -1,28 +1,30 @@ +@import '../../../assets/tailwind.css'; + .stat_block { - @apply flex items-center rounded-xl shadow-lg relative w-full; + @apply relative flex w-full items-center rounded-xl shadow-lg; /* @apply col-span-3 md:col-span-2 lg:col-span-6; */ @apply p-8; & > .status_bar { - @apply w-1 h-full absolute left-0 top-0 rounded-l sm:hidden; + @apply absolute top-0 left-0 h-full w-1 rounded-l sm:hidden; } & > .icon { - @apply hidden flex-shrink-0 items-center justify-center rounded-lg shadow-md w-12 h-12; + @apply hidden h-12 w-12 shrink-0 items-center justify-center rounded-lg shadow-md; @apply transition-colors duration-500; - @apply sm:flex sm:mr-4; + @apply sm:mr-4 sm:flex; & > svg { - @apply w-6 h-6 m-auto; + @apply m-auto h-6 w-6; } } } .terminal { - @apply flex flex-col w-full h-full relative; + @apply relative flex h-full w-full flex-col; - & > .container { - @apply min-h-[453px] rounded-t-xl pt-8 pl-8 bg-[#131313] border-x-[1px] border-[#ffffff11] border-t-[1px] font-mono text-sm h-full; + & > .terminalContainer { + @apply h-full min-h-[453px] rounded-t-xl border-x-[1px] border-t-[1px] border-[#ffffff11] bg-[#131313] pt-8 pl-8 font-mono text-sm; & #terminal { @apply h-full w-full; @@ -38,16 +40,16 @@ } & .command_icon { - @apply flex items-center top-0 left-0 absolute z-10 select-none h-full px-3 transition-colors duration-100; + @apply absolute top-0 left-0 z-10 flex h-full items-center px-3 transition-colors duration-100 select-none; } & .command_input { - @apply relative bg-[#131313] border-[#ffffff11] border-x-[1px] border-b-[1px] px-4 pb-6 pt-2 text-zinc-100 pl-8 pr-4 w-full font-mono text-sm rounded-b-xl; - @apply focus:ring-0 outline-none focus-visible:outline-none; - @apply active:border-b-blue-500 focus:border-b-blue-500; + @apply relative w-full rounded-b-xl border-x-[1px] border-b-[1px] border-[#ffffff11] bg-[#131313] px-4 pt-2 pr-4 pb-6 pl-8 font-mono text-sm text-zinc-100; + @apply outline-hidden focus:ring-0 focus-visible:outline-none; + @apply focus:border-b-blue-500 active:border-b-blue-500; } } .chart_container { - @apply bg-[#131313] rounded-xl border-[#ffffff11] border-[1px] relative; + @apply relative rounded-xl border-[1px] border-[#ffffff11] bg-[#131313]; } diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx deleted file mode 100644 index a0984cf48..000000000 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Form, Formik, FormikHelpers } from 'formik'; -import { useState } from 'react'; -import { object, string } from 'yup'; - -import FlashMessageRender from '@/components/FlashMessageRender'; -import Field from '@/components/elements/Field'; -import Modal from '@/components/elements/Modal'; -import { Button } from '@/components/elements/button/index'; - -import { httpErrorToHuman } from '@/api/http'; -import createServerDatabase from '@/api/server/databases/createServerDatabase'; - -import { ServerContext } from '@/state/server'; - -import useFlash from '@/plugins/useFlash'; - -interface Values { - databaseName: string; - connectionsFrom: string; -} - -const schema = object().shape({ - databaseName: string() - .required('A database name must be provided.') - .min(3, 'Database name must be at least 3 characters.') - .max(48, 'Database name must not exceed 48 characters.') - .matches( - /^[\w\-.]{3,48}$/, - 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.', - ), - connectionsFrom: string().matches(/^[\w\-/.%:]+$/, 'A valid host address must be provided.'), -}); - -export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const { addError, clearFlashes } = useFlash(); - const [visible, setVisible] = useState(false); - - const appendDatabase = ServerContext.useStoreActions((actions) => actions.databases.appendDatabase); - - const submit = (values: Values, { setSubmitting }: FormikHelpers) => { - clearFlashes('database:create'); - createServerDatabase(uuid, { - databaseName: values.databaseName, - connectionsFrom: values.connectionsFrom || '%', - }) - .then((database) => { - appendDatabase(database); - setVisible(false); - }) - .catch((error) => { - addError({ key: 'database:create', message: httpErrorToHuman(error) }); - setSubmitting(false); - }); - }; - - return ( - <> - - {({ isSubmitting, resetForm }) => ( - { - resetForm(); - setVisible(false); - }} - title='Create new database' - > -
- -
- -
- -
-
- -
- -
-
- )} -
- - - ); -}; diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 942b78976..c7aad6d57 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -1,18 +1,20 @@ -import { faDatabase, faEye, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Form, Formik, FormikHelpers } from 'formik'; -import { For } from 'million/react'; import { useState } from 'react'; import styled from 'styled-components'; import { object, string } from 'yup'; import FlashMessageRender from '@/components/FlashMessageRender'; +import ActionButton from '@/components/elements/ActionButton'; import Can from '@/components/elements/Can'; import CopyOnClick from '@/components/elements/CopyOnClick'; import Field from '@/components/elements/Field'; import Input from '@/components/elements/Input'; import Modal from '@/components/elements/Modal'; -import { Button } from '@/components/elements/button/index'; +import Spinner from '@/components/elements/Spinner'; +import HugeIconsDatabase from '@/components/elements/hugeicons/Database'; +import HugeIconsEye from '@/components/elements/hugeicons/Eye'; +import HugeIconsTrash from '@/components/elements/hugeicons/Trash'; +import { PageListItem } from '@/components/elements/pages/PageList'; import RotatePasswordButton from '@/components/server/databases/RotatePasswordButton'; import { httpErrorToHuman } from '@/api/http'; @@ -34,7 +36,7 @@ interface Props { database: ServerDatabase; } -export default ({ database }: Props) => { +const DatabaseRow = ({ database }: Props) => { const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); const { addError, clearFlashes } = useFlash(); const [visible, setVisible] = useState(false); @@ -53,17 +55,23 @@ export default ({ database }: Props) => { .oneOf([database.name.split('_', 2)[1] || '', database.name], 'The database name must be provided.'), }); - const submit = (_: { confirm: string }, { setSubmitting }: FormikHelpers<{ confirm: string }>) => { + const submit = (_: { confirm: string }, { setSubmitting, resetForm }: FormikHelpers<{ confirm: string }>) => { clearFlashes(); deleteServerDatabase(uuid, database.id) .then(() => { + resetForm(); setVisible(false); setTimeout(() => removeDatabase(database.id), 150); + setSubmitting(false); }) .catch((error) => { + resetForm(); console.error(error); setSubmitting(false); - addError({ key: 'database:delete', message: httpErrorToHuman(error) }); + addError({ + key: 'database:delete', + message: httpErrorToHuman(error), + }); }); }; @@ -95,9 +103,15 @@ export default ({ database }: Props) => { label={'Confirm Database Name'} description={'Enter the database name to confirm deletion.'} /> - + + {isSubmitting && } + {isSubmitting ? 'Deleting...' : 'Delete Database'} +
@@ -161,74 +175,68 @@ export default ({ database }: Props) => {
- {/* Title */} -
-
- -
- -

{database.name}

-
- -

{database.connectionString}

-
+ +
+
+
+
+ +
+
+ +

{database.name}

+
+
+
+ +
+
+

Endpoint

+ +

{database.connectionString}

+
+
+
+

From

+ +

{database.allowConnectionsFrom}

+
+
+
+

Username

+ +

{database.username}

+
+
+
+
+ +
+ setConnectionVisible(true)} + className='flex items-center gap-2' + > + + Details + + + setVisible(true)} + className='flex items-center gap-2' + > + + Delete + +
-
- - {/* Properties + buttons */} -
-
- - {(db, index) => ( -
- -

{db.value}

-
-

{db.label}

-
- )} -
-
- -
- - - - -
- {/* - - - */} -
+ ); }; + +export default DatabaseRow; diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index ec9580d43..3b92664a9 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -1,15 +1,21 @@ +import { Form, Formik, FormikHelpers } from 'formik'; import { For } from 'million/react'; import { useEffect, useState } from 'react'; +import { object, string } from 'yup'; import FlashMessageRender from '@/components/FlashMessageRender'; +import ActionButton from '@/components/elements/ActionButton'; import Can from '@/components/elements/Can'; +import Field from '@/components/elements/Field'; import { MainPageHeader } from '@/components/elements/MainPageHeader'; +import Modal from '@/components/elements/Modal'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; +import HugeIconsDatabase from '@/components/elements/hugeicons/Database'; import { PageListContainer, PageListItem } from '@/components/elements/pages/PageList'; -import CreateDatabaseButton from '@/components/server/databases/CreateDatabaseButton'; import DatabaseRow from '@/components/server/databases/DatabaseRow'; import { httpErrorToHuman } from '@/api/http'; +import createServerDatabase from '@/api/server/databases/createServerDatabase'; import getServerDatabases from '@/api/server/databases/getServerDatabases'; import { ServerContext } from '@/state/server'; @@ -17,15 +23,52 @@ import { ServerContext } from '@/state/server'; import { useDeepMemoize } from '@/plugins/useDeepMemoize'; import useFlash from '@/plugins/useFlash'; -export default () => { +interface DatabaseValues { + databaseName: string; + connectionsFrom: string; +} + +const databaseSchema = object().shape({ + databaseName: string() + .required('A database name must be provided.') + .min(3, 'Database name must be at least 3 characters.') + .max(48, 'Database name must not exceed 48 characters.') + .matches( + /^[\w\-.]{3,48}$/, + 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.', + ), + connectionsFrom: string().matches(/^[\w\-/.%:]+$/, 'A valid host address must be provided.'), +}); + +const DatabasesContainer = () => { const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); const databaseLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.databases); const { addError, clearFlashes } = useFlash(); const [loading, setLoading] = useState(true); + const [createModalVisible, setCreateModalVisible] = useState(false); const databases = useDeepMemoize(ServerContext.useStoreState((state) => state.databases.data)); const setDatabases = ServerContext.useStoreActions((state) => state.databases.setDatabases); + const appendDatabase = ServerContext.useStoreActions((actions) => actions.databases.appendDatabase); + + const submitDatabase = (values: DatabaseValues, { setSubmitting, resetForm }: FormikHelpers) => { + clearFlashes('database:create'); + createServerDatabase(uuid, { + databaseName: values.databaseName, + connectionsFrom: values.connectionsFrom || '%', + }) + .then((database) => { + resetForm(); + appendDatabase(database); + setSubmitting(false); + setCreateModalVisible(false); + }) + .catch((error) => { + addError({ key: 'database:create', message: httpErrorToHuman(error) }); + setSubmitting(false); + }); + }; useEffect(() => { setLoading(!databases.length); @@ -43,40 +86,109 @@ export default () => { return ( - - -
- {databaseLimit > 0 && databases.length > 0 && ( -

- {databases.length} of {databaseLimit} databases -

- )} - {databaseLimit > 0 && databaseLimit !== databases.length && } -
-
+ +
+ {databaseLimit > 0 && ( +

+ {databases.length} of {databaseLimit} databases +

+ )} + {databaseLimit > 0 && databaseLimit !== databases.length && ( + setCreateModalVisible(true)}> + New Database + + )} +
+ + } + > +

+ Create and manage MySQL databases for your server. Configure database access, manage users, and view + connection details. +

- {!databases.length && loading ? null : ( - <> - {databases.length > 0 ? ( - - - {(database, index) => ( - - - - )} - - - ) : ( -

+ + {({ isSubmitting, resetForm }) => ( + { + resetForm(); + setCreateModalVisible(false); + }} + title='Create new database' + > +

+ +
+ +
+ +
+
+ + Create Database + +
+ +
+ + )} + + + {!databases.length && loading ? ( +
+
+
+ ) : databases.length > 0 ? ( + + + {(database, index) => } + + + ) : ( +
+
+
+ +
+

+ {databaseLimit > 0 ? 'No databases found' : 'Databases unavailable'} +

+

{databaseLimit > 0 - ? 'Your server does not have any databases.' + ? 'Your server does not have any databases. Create one to get started.' : 'Databases cannot be created for this server.'}

- )} - +
+
)}
); }; + +export default DatabasesContainer; diff --git a/resources/scripts/components/server/databases/RotatePasswordButton.tsx b/resources/scripts/components/server/databases/RotatePasswordButton.tsx index c4d72f0de..ce92617fb 100644 --- a/resources/scripts/components/server/databases/RotatePasswordButton.tsx +++ b/resources/scripts/components/server/databases/RotatePasswordButton.tsx @@ -1,10 +1,9 @@ -import { faRotateRight } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Actions, useStoreActions } from 'easy-peasy'; import { useState } from 'react'; +import ActionButton from '@/components/elements/ActionButton'; import Spinner from '@/components/elements/Spinner'; -import { Button } from '@/components/elements/button/index'; +import HugeIconsRefresh from '@/components/elements/hugeicons/Refresh'; import { httpErrorToHuman } from '@/api/http'; import { ServerDatabase } from '@/api/server/databases/getServerDatabases'; @@ -13,7 +12,13 @@ import rotateDatabasePassword from '@/api/server/databases/rotateDatabasePasswor import { ApplicationStore } from '@/state'; import { ServerContext } from '@/state/server'; -export default ({ databaseId, onUpdate }: { databaseId: string; onUpdate: (database: ServerDatabase) => void }) => { +const RotatePasswordButton = ({ + databaseId, + onUpdate, +}: { + databaseId: string; + onUpdate: (database: ServerDatabase) => void; +}) => { const [loading, setLoading] = useState(false); const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const server = ServerContext.useStoreState((state) => state.server.data!); @@ -45,11 +50,13 @@ export default ({ databaseId, onUpdate }: { databaseId: string; onUpdate: (datab }; return ( - + ); }; + +export default RotatePasswordButton; diff --git a/resources/scripts/components/server/features/Features.tsx b/resources/scripts/components/server/features/Features.tsx index e1b2152bb..11eb880b5 100644 --- a/resources/scripts/components/server/features/Features.tsx +++ b/resources/scripts/components/server/features/Features.tsx @@ -7,7 +7,7 @@ import features from './index'; type ListItems = [string, ComponentType][]; -export default ({ enabled }: { enabled: string[] }) => { +const Features = ({ enabled }: { enabled: string[] }) => { const mapped: ListItems = useMemo(() => { return getObjectKeys(features) .filter((key) => enabled.map((v) => v.toLowerCase()).includes(key.toLowerCase())) @@ -22,3 +22,5 @@ export default ({ enabled }: { enabled: string[] }) => { ); }; + +export default Features; diff --git a/resources/scripts/components/server/features/GSLTokenModalFeature.tsx b/resources/scripts/components/server/features/GSLTokenModalFeature.tsx index 7131fc3c7..2e8877e26 100644 --- a/resources/scripts/components/server/features/GSLTokenModalFeature.tsx +++ b/resources/scripts/components/server/features/GSLTokenModalFeature.tsx @@ -2,9 +2,9 @@ import { Form, Formik } from 'formik'; import { useEffect, useState } from 'react'; import FlashMessageRender from '@/components/FlashMessageRender'; +import ActionButton from '@/components/elements/ActionButton'; import Field from '@/components/elements/Field'; import Modal from '@/components/elements/Modal'; -import { Button } from '@/components/elements/button/index'; import { SocketEvent, SocketRequest } from '@/components/server/events'; import updateStartupVariable from '@/api/server/updateStartupVariable'; @@ -93,7 +93,9 @@ const GSLTokenModalFeature = () => { />
- + + Update GSL Token +
diff --git a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx index b85ce353c..c20aa885e 100644 --- a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx +++ b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; +import ActionButton from '@/components/elements/ActionButton'; // import { Options } from '@/components/elements/button/types'; import Can from '@/components/elements/Can'; import { @@ -11,7 +12,6 @@ import { } from '@/components/elements/DropdownMenu'; import Modal from '@/components/elements/Modal'; import Spinner from '@/components/elements/Spinner'; -import { Button } from '@/components/elements/button/index'; import HugeIconsArrowDown from '@/components/elements/hugeicons/ArrowDown'; import HugeIconsArrowUp from '@/components/elements/hugeicons/ArrowUp'; import { SocketEvent, SocketRequest } from '@/components/server/events'; @@ -102,7 +102,7 @@ const JavaVersionModalFeature = () => { setDropDownOpen(open)}> - + {data && Object.keys(data.dockerImages).map((key) => ( @@ -137,9 +137,9 @@ const JavaVersionModalFeature = () => { Cancel */} - +
diff --git a/resources/scripts/components/server/features/MclogsFeature.tsx b/resources/scripts/components/server/features/MclogsFeature.tsx new file mode 100644 index 000000000..0e1870326 --- /dev/null +++ b/resources/scripts/components/server/features/MclogsFeature.tsx @@ -0,0 +1,503 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { toast } from 'sonner'; + +import ActionButton from '@/components/elements/ActionButton'; +import Modal from '@/components/elements/Modal'; +import Spinner from '@/components/elements/Spinner'; +import { Alert } from '@/components/elements/alert'; +import HugeIconsAlert from '@/components/elements/hugeicons/Alert'; +import HugeIconsCheck from '@/components/elements/hugeicons/Check'; +import HugeIconsLink from '@/components/elements/hugeicons/Link'; +import { SocketEvent } from '@/components/server/events'; + +import { debounce, isCrashLine } from '@/lib/mclogsUtils'; + +import { MclogsInsight, analyzeLogs } from '@/api/mclo.gs/mclogsApi'; +import getFileContents from '@/api/server/files/getFileContents'; + +import { ServerContext } from '@/state/server'; + +import useWebsocketEvent from '@/plugins/useWebsocketEvent'; + +const CRASH_DETECTION_DEBOUNCE = 1500; // 1.5 seconds +const MANUAL_ANALYZE_DEBOUNCE = 1000; // 1 second for manual clicks +const LOG_FILE_PATH = '/logs/latest.log'; +const MAX_CONSOLE_BUFFER = 300; + +// Shared analysis logic hook +const useLogAnalysis = () => { + const [analyzing, setAnalyzing] = useState(false); + const [analysis, setAnalysis] = useState(null); + const [error, setError] = useState(null); + const [showCard, setShowCard] = useState(false); + + const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const status = ServerContext.useStoreState((state) => state.status.value); + + const consoleBufferRef = useRef([]); + const previousStatusRef = useRef(status); + const mountedRef = useRef(true); + + // Keep console buffer trimmed and split chunks into lines + useWebsocketEvent(SocketEvent.CONSOLE_OUTPUT, (data: string) => { + const lines = String(data).split(/\r?\n/).filter(Boolean); + if (lines.length === 0) return; + + consoleBufferRef.current.push(...lines); + if (consoleBufferRef.current.length > MAX_CONSOLE_BUFFER) { + // Trim to last MAX_CONSOLE_BUFFER lines + consoleBufferRef.current = consoleBufferRef.current.slice(-MAX_CONSOLE_BUFFER); + } + }); + + const analyzeCrash = useCallback( + async (showToast = false) => { + setAnalyzing(true); + setError(null); + + try { + const logContent = await getFileContents(uuid, LOG_FILE_PATH); + + if (!logContent || logContent.trim().length === 0) { + throw new Error('No log content found in latest.log'); + } + + const result = await analyzeLogs(logContent); + if (!mountedRef.current) return; + + setAnalysis(result); + setShowCard(true); + + // Show toast notifications for manual analysis + if (showToast) { + if (result.analysis?.problems?.length > 0) { + toast.success(`Analysis complete - ${result.analysis.problems.length} issue(s) found`); + } else { + toast.info('Analysis complete - no specific issues detected'); + } + } + } catch (err) { + if (!mountedRef.current) return; + + const errorMessage = err instanceof Error ? err.message : 'Failed to analyze server logs'; + setError(errorMessage); + console.error('Mclogs analysis failed:', err); + + // Show card even on error for auto-analysis + setShowCard(true); + + // Only show error toast for manual analysis and unexpected errors + const looksLikeMissingLog = + /latest\.log/i.test(errorMessage) || + /not found/i.test(errorMessage) || + /no log content/i.test(errorMessage); + + if (!looksLikeMissingLog && showToast) { + toast.error('Failed to analyze server logs'); + } + } finally { + if (mountedRef.current) setAnalyzing(false); + } + }, + [uuid], + ); + + // Debounced auto-analysis used when we detect crash indicators + const analyzeCrashDebounced = useMemo(() => { + const fn = debounce(() => { + // Note: run the immediate version internally + void analyzeCrash(); + }, CRASH_DETECTION_DEBOUNCE); + + return fn; + }, [analyzeCrash]); + + // Monitor server status changes to detect crashes + useEffect(() => { + // If server just went offline, check recent console output for crash indicators + if (previousStatusRef.current !== 'offline' && status === 'offline') { + const hasCrashIndicators = consoleBufferRef.current.some((line) => isCrashLine(line)); + if (hasCrashIndicators) { + analyzeCrashDebounced(); + } + } + + // Update previous status + previousStatusRef.current = status; + }, [status, analyzeCrashDebounced]); + + // Manual analysis (debounced to prevent rapid clicking) + const manualAnalyze = useMemo(() => { + return debounce(() => { + void analyzeCrash(true); // Show toast for manual analysis + }, MANUAL_ANALYZE_DEBOUNCE); + }, [analyzeCrash]); + + // Dismiss card + const dismissCard = () => { + setShowCard(false); + }; + + // Cleanup on unmount + useEffect(() => { + mountedRef.current = true; + return () => { + mountedRef.current = false; + // Best-effort cancel if debounce util provides cancel() + try { + (analyzeCrashDebounced as { cancel?: () => void })?.cancel?.(); + (manualAnalyze as { cancel?: () => void })?.cancel?.(); + } catch { + // no-op + } + }; + }, [analyzeCrashDebounced, manualAnalyze]); + + return { + analyzing, + analysis, + error, + showCard, + manualAnalyze, + dismissCard, + consoleBufferRef, + previousStatusRef, + mountedRef, + analyzeCrashDebounced, + }; +}; + +// Crash Analysis Card Component +export const CrashAnalysisCard = () => { + const { analyzing, analysis, error, showCard, dismissCard } = useLogAnalysis(); + + const [modalVisible, setModalVisible] = useState(false); + + if (!showCard) return null; + + const getCardMessage = () => { + if (analyzing) { + return 'Analyzing server crash logs...'; + } + + if (error) { + const looksLikeMissingLog = + /latest\.log/i.test(error) || /not found/i.test(error) || /no log content/i.test(error); + + if (looksLikeMissingLog) { + return 'Server crashed but no log file was found. Try running the server to generate logs.'; + } + return 'Server crashed but analysis failed. Check the logs manually.'; + } + + if (!analysis) { + return 'Server crashed. Analysis in progress...'; + } + + const problems = analysis.analysis?.problems ?? []; + if (problems.length > 0) { + return `We analyzed your server and found ${problems.length} issue${problems.length === 1 ? '' : 's'}.`; + } + + return 'We analyzed your server crash but found no specific issues. This may be due to configuration or resource limitations.'; + }; + + const getCardType = (): 'warning' | 'danger' => { + if (analyzing) return 'warning'; + if (error) return 'danger'; + if (!analysis) return 'warning'; + const problems = analysis.analysis?.problems ?? []; + return problems.length > 0 ? 'danger' : 'warning'; + }; + + const canViewAnalysis = analysis && !error && !analyzing; + + return ( + <> +
+ +
+
+

Crash Analysis

+

{getCardMessage()}

+
+
+ {canViewAnalysis && ( + setModalVisible(true)} size='sm'> + View Details + + )} + + Dismiss + +
+
+
+
+ + {/* Analysis Modal */} + {modalVisible && ( + setModalVisible(false)} + analysis={analysis} + error={error} + analyzing={analyzing} + /> + )} + + ); +}; + +// Analysis Modal Component +const AnalysisModal = ({ + visible, + onClose, + analysis, + error, + analyzing, +}: { + visible: boolean; + onClose: () => void; + analysis: MclogsInsight | null; + error: string | null; + analyzing: boolean; +}) => { + const { manualAnalyze } = useLogAnalysis(); + + const closeModal = () => { + if (analyzing) return; + onClose(); + }; + + // Render loading state + const renderLoadingState = () => ( +
+ +

Analyzing Server Logs

+

+ We're analyzing your server logs with mclo.gs to identify potential issues and provide solutions. +

+
+ ); + + // Render error state + const renderErrorState = () => ( +
+
+
+ +
+

Analysis Failed

+

{error}

+ {(/latest\.log/i.test(error!) || /no log content/i.test(error!)) && ( +

+ This usually means the log file doesn't exist yet. Try starting your server to + generate logs first. +

+ )} +
+
+
+
+ ); + + // Render server information header + const renderServerInfo = () => { + if (!analysis) return null; + + const information = analysis.analysis?.information ?? []; + const serverVersion = analysis.version; + const serverType = analysis.title; + + return ( +
+
+

Server Information

+ + + Powered by mclo.gs + +
+ +
+
+

Server Type

+

+ {serverType} {serverVersion} +

+
+ + {information.slice(0, 3).map((info, idx) => ( +
+

{info.label}

+

{info.value}

+
+ ))} +
+ + {information.length > 3 && ( +
+ + Show {information.length - 3} more details + +
+ {information.slice(3).map((info, idx) => ( +
+

{info.label}

+

{info.value}

+
+ ))} +
+
+ )} +
+ ); + }; + + // Render errors section + const renderErrors = () => { + if (!analysis) return null; + + const problems = analysis.analysis?.problems ?? []; + + if (problems.length === 0) { + return ( +
+
+ +
+

No Issues Detected

+

+ No specific issues were found in your server logs. The crash may be due to configuration + problems or resource limitations. +

+
+
+
+ ); + } + + return ( +
+

Issues Found ({problems.length})

+ +
+ {problems.map((problem, idx) => ( +
+
+
+ +
+

{problem.message}

+ + {!!problem.entry?.lines?.length && ( +
+

Error Log:

+
+ {problem.entry.lines.map((line, lineIdx) => ( +
+ + {line.number} + + + {line.content} + +
+ ))} +
+
+ )} +
+
+
+
+ ))} +
+
+ ); + }; + + // Render recommendations section + const renderRecommendations = () => { + if (!analysis) return null; + + const problems = analysis.analysis?.problems ?? []; + const allSolutions = problems.flatMap((problem) => problem.solutions || []); + + if (allSolutions.length === 0) return null; + + return ( +
+

Recommended Solutions ({allSolutions.length})

+ +
+
+ {allSolutions.map((solution, idx) => ( +
+
+ +
+
+

{solution.message}

+
+
+ ))} +
+
+
+ ); + }; + + // Main content renderer + const renderContent = () => { + if (analyzing) return renderLoadingState(); + if (error) return renderErrorState(); + if (!analysis) { + return ( +
+

No analysis data available

+
+ ); + } + + return ( +
+ {renderServerInfo()} + {renderErrors()} + {renderRecommendations()} +
+ ); + }; + + return ( + +
+ {renderContent()} + +
+ + {analyzing ? 'Analyzing...' : 'Analyze Again'} + + + Close + +
+
+
+ ); +}; diff --git a/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx b/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx index 6c115cb06..71b816fd4 100644 --- a/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx +++ b/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx @@ -60,7 +60,7 @@ const SteamDiskSpaceFeature = () => {

Ensure the machine has enough disk space by typing{' '} - df -h on the machine + df -h on the machine hosting this server. Delete files or increase the available disk space to resolve the issue.

diff --git a/resources/scripts/components/server/features/eula/EulaModalFeature.tsx b/resources/scripts/components/server/features/eula/EulaModalFeature.tsx index 0d11b69a1..75e5057b9 100644 --- a/resources/scripts/components/server/features/eula/EulaModalFeature.tsx +++ b/resources/scripts/components/server/features/eula/EulaModalFeature.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; import FlashMessageRender from '@/components/FlashMessageRender'; +import ActionButton from '@/components/elements/ActionButton'; import Modal from '@/components/elements/Modal'; -import { Button } from '@/components/elements/button/index'; import { SocketEvent, SocketRequest } from '@/components/server/events'; import saveFileContents from '@/api/server/files/saveFileContents'; @@ -83,8 +83,12 @@ const EulaModalFeature = () => { .

- setVisible(false)}>I don't accept - + setVisible(false)}> + I don't accept + + + I accept +
diff --git a/resources/scripts/components/server/files/ChmodFileModal.tsx b/resources/scripts/components/server/files/ChmodFileModal.tsx index 24610ae91..608b12c44 100644 --- a/resources/scripts/components/server/files/ChmodFileModal.tsx +++ b/resources/scripts/components/server/files/ChmodFileModal.tsx @@ -1,9 +1,9 @@ import { fileBitsToString } from '@/helpers'; import { Form, Formik, FormikHelpers } from 'formik'; +import ActionButton from '@/components/elements/ActionButton'; import Field from '@/components/elements/Field'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; -import { Button } from '@/components/elements/button/index'; import chmodFiles from '@/api/server/files/chmodFiles'; @@ -78,7 +78,9 @@ const ChmodFileModal = ({ files, ...props }: OwnProps) => { />
- + + Update +
diff --git a/resources/scripts/components/server/files/FileDropdownMenu.tsx b/resources/scripts/components/server/files/FileDropdownMenu.tsx index 3ae1d3701..d03d5530c 100644 --- a/resources/scripts/components/server/files/FileDropdownMenu.tsx +++ b/resources/scripts/components/server/files/FileDropdownMenu.tsx @@ -126,22 +126,22 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { setModal('rename')}> - + Rename setModal('move')}> - + Move setModal('chmod')}> - + Permissions {file.isFile && ( - + Duplicate @@ -149,27 +149,27 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { {file.isArchiveType() ? ( - + Unarchive ) : ( - + Archive )} {file.isFile && ( - + Download )} setShowConfirmation(true)}> - + Delete diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 912570528..75ad2aa3a 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -9,6 +9,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { toast } from 'sonner'; import FlashMessageRender from '@/components/FlashMessageRender'; +import ActionButton from '@/components/elements/ActionButton'; import Can from '@/components/elements/Can'; import { DropdownMenu, @@ -31,7 +32,7 @@ import useFlash from '@/plugins/useFlash'; const Editor = lazy(() => import('@/components/elements/editor/Editor')); -export default () => { +const FileEditContainer = () => { const [error, setError] = useState(''); const { action, '*': rawFilename } = useParams<{ action: 'edit' | 'new'; '*': string }>(); const [_, setLoading] = useState(action === 'edit'); @@ -121,7 +122,7 @@ export default () => { } return ( - + @@ -134,14 +135,14 @@ export default () => { {filename === '.pteroignore' ? ( -
+

You're editing a{' '} - .pteroignore file. Any files or - directories listed in here will be excluded from backups. Wildcards are supported by using an - asterisk (*). You can negate a - prior rule by prepending an exclamation point ( - !). + .pteroignore file. Any files + or directories listed in here will be excluded from backups. Wildcards are supported by using an + asterisk (*). You can negate + a prior rule by prepending an exclamation point ( + !).

) : null} @@ -156,7 +157,7 @@ export default () => { />
div>div]:h-full [&>div>div]:!outline-none w-full`} + className={`relative h-full bg-[#ffffff11] border-[1px] border-[#ffffff07] border-t-0 [&>div>div]:h-full [&>div>div]:outline-hidden! w-full`} > { strokeLinejoin='round' /> - {language?.name ?? 'Language'} + {language?.name ?? 'Language'} { /> - + a.name.localeCompare(b.name))} memo> {(language) => ( { {action === 'edit' ? (
- + Save{' '} + + CTRL + S + + - - + - - + + + + saveAndRestart()}> @@ -281,19 +283,14 @@ export default () => { ) : ( - + )}
); }; + +export default FileEditContainer; diff --git a/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx b/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx index e3f3f7cdc..ade1e6b03 100644 --- a/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx +++ b/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx @@ -10,7 +10,7 @@ interface Props { isNewFile?: boolean; } -export default ({ renderLeft, withinFileEditor, isNewFile }: Props) => { +const FileManagerBreadcrumbs = ({ renderLeft, withinFileEditor, isNewFile }: Props) => { const id = ServerContext.useStoreState((state) => state.server.data!.id); const directory = ServerContext.useStoreState((state) => state.files.directory); @@ -46,7 +46,7 @@ export default ({ renderLeft, withinFileEditor, isNewFile }: Props) => { }; return ( -
+
{renderLeft ||
} root @@ -95,3 +95,5 @@ export default ({ renderLeft, withinFileEditor, isNewFile }: Props) => {
); }; + +export default FileManagerBreadcrumbs; diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index 21755115e..2b1db6280 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -35,7 +35,7 @@ const sortFiles = (files: FileObject[]): FileObject[] => { return sortedFiles.filter((file, index) => index === 0 || file.name !== sortedFiles[index - 1]?.name); }; -export default () => { +const FileManagerContainer = () => { const parentRef = useRef(null); const id = ServerContext.useStoreState((state) => state.server.data!.id); @@ -86,10 +86,6 @@ export default () => { } }, [hash, pathname, directory]); - if (error) { - return ; - } - const rowVirtualizer = useVirtualizer({ // count: 10000, count: filesArray.length, @@ -98,19 +94,32 @@ export default () => { // scrollMargin: 54, }); + if (error) { + return ; + } + return ( - +
- - -
- - - - -
-
+ +
+ + + + +
+ + } + > +

+ Manage your server files and directories. Upload, download, edit, and organize your + server's file system with our integrated file manager. +

{ }} > @@ -212,3 +221,5 @@ export default () => { ); }; + +export default FileManagerContainer; diff --git a/resources/scripts/components/server/files/FileManagerStatus.tsx b/resources/scripts/components/server/files/FileManagerStatus.tsx index c75566f2b..2f2a45462 100644 --- a/resources/scripts/components/server/files/FileManagerStatus.tsx +++ b/resources/scripts/components/server/files/FileManagerStatus.tsx @@ -4,8 +4,8 @@ import { useSignal } from '@preact/signals-react'; import { useContext, useEffect } from 'react'; +import ActionButton from '@/components/elements/ActionButton'; import Code from '@/components/elements/Code'; -import { Button } from '@/components/elements/button/index'; import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; import asDialog from '@/hoc/asDialog'; @@ -45,26 +45,30 @@ const FileUploadList = () => { return (
{uploads.map(([name, file]) => ( -
+
{/* */} -
+
{/* */} {name} - + Cancel +
))} - clearFileUploads()}> + clearFileUploads()}> Cancel Uploads - - Close + + + Close +
); @@ -75,7 +79,7 @@ const FileUploadListDialog = asDialog({ description: 'The following files are being uploaded to your server.', })(FileUploadList); -export default () => { +const FileManagerStatus = () => { const open = useSignal(false); const count = ServerContext.useStoreState((state) => Object.keys(state.files.uploads).length); @@ -90,9 +94,14 @@ export default () => { <> {count > 0 && ( // - + // )} (open.value = false)} /> ); }; + +export default FileManagerStatus; diff --git a/resources/scripts/components/server/files/FileNameModal.tsx b/resources/scripts/components/server/files/FileNameModal.tsx index 48016d15c..c949bd062 100644 --- a/resources/scripts/components/server/files/FileNameModal.tsx +++ b/resources/scripts/components/server/files/FileNameModal.tsx @@ -2,9 +2,9 @@ import { Form, Formik, FormikHelpers } from 'formik'; import { join } from 'pathe'; import { object, string } from 'yup'; +import ActionButton from '@/components/elements/ActionButton'; import Field from '@/components/elements/Field'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; -import { Button } from '@/components/elements/button/index'; import { ServerContext } from '@/state/server'; @@ -16,7 +16,7 @@ interface Values { fileName: string; } -export default ({ onFileNamed, onDismissed, ...props }: Props) => { +const FileNameModal = ({ onFileNamed, onDismissed, ...props }: Props) => { const directory = ServerContext.useStoreState((state) => state.files.directory); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { @@ -50,7 +50,7 @@ export default ({ onFileNamed, onDismissed, ...props }: Props) => { autoFocus />
- + Create File
@@ -58,3 +58,5 @@ export default ({ onFileNamed, onDismissed, ...props }: Props) => { ); }; + +export default FileNameModal; diff --git a/resources/scripts/components/server/files/MassActionsBar.tsx b/resources/scripts/components/server/files/MassActionsBar.tsx index 27c25c2ab..163c26c45 100644 --- a/resources/scripts/components/server/files/MassActionsBar.tsx +++ b/resources/scripts/components/server/files/MassActionsBar.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from 'react'; +import ActionButton from '@/components/elements/ActionButton'; +import Spinner from '@/components/elements/Spinner'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; -import { Button } from '@/components/elements/button/index'; import { Dialog } from '@/components/elements/dialog'; import FadeTransition from '@/components/elements/transitions/FadeTransition'; import RenameFileModal from '@/components/server/files/RenameFileModal'; @@ -74,6 +75,7 @@ const MassActionsBar = () => { confirm={'Delete'} onClose={() => setShowConfirm(false)} onConfirmed={onClickConfirmDeletion} + loading={loading} >

Are you sure you want to delete  @@ -100,12 +102,19 @@ const MassActionsBar = () => { 'pointer-events-none fixed bottom-0 left-0 right-0 mb-6 flex justify-center w-full z-50' } > -

- - - setShowConfirm(true)}> +
+ setShowMove(true)} disabled={loading}> + {loading && loadingMessage.includes('Moving') && } + Move + + + {loading && loadingMessage.includes('Archiving') && } + Archive + + setShowConfirm(true)} disabled={loading}> + {loading && loadingMessage.includes('Deleting') && } Delete - +
diff --git a/resources/scripts/components/server/files/NewDirectoryButton.tsx b/resources/scripts/components/server/files/NewDirectoryButton.tsx index 3ae0453e9..324b44274 100644 --- a/resources/scripts/components/server/files/NewDirectoryButton.tsx +++ b/resources/scripts/components/server/files/NewDirectoryButton.tsx @@ -4,16 +4,16 @@ import { useContext, useEffect, useState } from 'react'; import { object, string } from 'yup'; import FlashMessageRender from '@/components/FlashMessageRender'; +import ActionButton from '@/components/elements/ActionButton'; import Code from '@/components/elements/Code'; import Field from '@/components/elements/Field'; -import { Button } from '@/components/elements/button/index'; import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; import asDialog from '@/hoc/asDialog'; import createDirectory from '@/api/server/files/createDirectory'; -import { FileObject } from '@/api/server/files/loadDirectory'; +// import { FileObject } from '@/api/server/files/loadDirectory'; import { ServerContext } from '@/state/server'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; @@ -27,20 +27,22 @@ const schema = object().shape({ directoryName: string().required('A valid directory name must be provided.'), }); -const generateDirectoryData = (name: string): FileObject => ({ - key: `dir_${name.split('/', 1)[0] ?? name}`, - name: name.replace(/^(\/*)/, '').split('/', 1)[0] ?? name, - mode: 'drwxr-xr-x', - modeBits: '0755', - size: 0, - isFile: false, - isSymlink: false, - mimetype: '', - createdAt: new Date(), - modifiedAt: new Date(), - isArchiveType: () => false, - isEditable: () => false, -}); +// removed to prevent linting issues, you're welcome. +// +// const generateDirectoryData = (name: string): FileObject => ({ +// key: `dir_${name.split('/', 1)[0] ?? name}`, +// name: name.replace(/^(\/*)/, '').split('/', 1)[0] ?? name, +// mode: 'drwxr-xr-x', +// modeBits: '0755', +// size: 0, +// isFile: false, +// isSymlink: false, +// mimetype: '', +// createdAt: new Date(), +// modifiedAt: new Date(), +// isArchiveType: () => false, +// isEditable: () => false, +// }); const NewDirectoryDialog = asDialog({ title: 'New Folder', @@ -60,7 +62,8 @@ const NewDirectoryDialog = asDialog({ const submit = ({ directoryName }: Values, { setSubmitting }: FormikHelpers) => { createDirectory(uuid, directory, directoryName) - .then(() => mutate((data) => [...data!, generateDirectoryData(directoryName)], false)) + // .then(() => mutate((data) => [...data!, generateDirectoryData(directoryName)], false)) + .then(() => mutate()) .then(() => close()) .catch((error) => { setSubmitting(false); @@ -75,7 +78,7 @@ const NewDirectoryDialog = asDialog({
-

+

This folder will be created as  /root/ @@ -86,12 +89,12 @@ const NewDirectoryDialog = asDialog({

- + Cancel - - + )} @@ -99,22 +102,17 @@ const NewDirectoryDialog = asDialog({ ); }); -export default () => { +const NewDirectoryButton = () => { const [open, setOpen] = useState(false); return ( <> - + ); }; + +export default NewDirectoryButton; diff --git a/resources/scripts/components/server/files/NewFileButton.tsx b/resources/scripts/components/server/files/NewFileButton.tsx index 7da36108e..762a67553 100644 --- a/resources/scripts/components/server/files/NewFileButton.tsx +++ b/resources/scripts/components/server/files/NewFileButton.tsx @@ -1,17 +1,15 @@ import { NavLink } from 'react-router-dom'; -export default ({ id }: { id: string }) => { +import ActionButton from '@/components/elements/ActionButton'; + +const NewFileButton = ({ id }: { id: string }) => { return ( -
+ New File -
+
); }; + +export default NewFileButton; diff --git a/resources/scripts/components/server/files/RenameFileModal.tsx b/resources/scripts/components/server/files/RenameFileModal.tsx index 375e16623..985be557c 100644 --- a/resources/scripts/components/server/files/RenameFileModal.tsx +++ b/resources/scripts/components/server/files/RenameFileModal.tsx @@ -1,10 +1,10 @@ import { Form, Formik, FormikHelpers } from 'formik'; import { join } from 'pathe'; +import ActionButton from '@/components/elements/ActionButton'; import Code from '@/components/elements/Code'; import Field from '@/components/elements/Field'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; -import { Button } from '@/components/elements/button/index'; import renameFiles from '@/api/server/files/renameFiles'; @@ -71,7 +71,7 @@ const RenameFileModal = ({ files, useMoveTerminology, ...props }: OwnProps) => {
{useMoveTerminology && ( -

+

New location: /root/ @@ -82,7 +82,9 @@ const RenameFileModal = ({ files, useMoveTerminology, ...props }: OwnProps) => {

)}
- + + {useMoveTerminology ? 'Move' : 'Rename'} +
diff --git a/resources/scripts/components/server/files/SelectFileCheckbox.tsx b/resources/scripts/components/server/files/SelectFileCheckbox.tsx index 460de7da4..1e123c767 100644 --- a/resources/scripts/components/server/files/SelectFileCheckbox.tsx +++ b/resources/scripts/components/server/files/SelectFileCheckbox.tsx @@ -2,7 +2,7 @@ import { Checkbox } from '@/components/elements/CheckboxNew'; import { ServerContext } from '@/state/server'; -export default ({ name }: { name: string }) => { +const SelectFileCheckbox = ({ name }: { name: string }) => { const isChecked = ServerContext.useStoreState((state) => state.files.selectedFiles.indexOf(name) >= 0); const appendSelectedFile = ServerContext.useStoreActions((actions) => actions.files.appendSelectedFile); const removeSelectedFile = ServerContext.useStoreActions((actions) => actions.files.removeSelectedFile); @@ -17,3 +17,5 @@ export default ({ name }: { name: string }) => { /> ); }; + +export default SelectFileCheckbox; diff --git a/resources/scripts/components/server/files/UploadButton.tsx b/resources/scripts/components/server/files/UploadButton.tsx index ccacbce2a..fb2cd1852 100644 --- a/resources/scripts/components/server/files/UploadButton.tsx +++ b/resources/scripts/components/server/files/UploadButton.tsx @@ -1,6 +1,7 @@ import axios from 'axios'; import { useEffect, useRef, useState } from 'react'; +import ActionButton from '@/components/elements/ActionButton'; import { ModalMask } from '@/components/elements/Modal'; import FadeTransition from '@/components/elements/transitions/FadeTransition'; @@ -20,7 +21,7 @@ function isFileOrDirectory(event: DragEvent): boolean { return event.dataTransfer.types.some((value) => value.toLowerCase() === 'files'); } -export default () => { +const UploadButton = () => { const fileUploadInput = useRef(null); const [timeouts, _] = useState([]); const [visible, setVisible] = useState(false); @@ -49,7 +50,7 @@ export default () => { useEventListener('dragexit', () => setVisible(false), { capture: true }); useEventListener('keydown', () => { - visible && setVisible(false); + if (visible) setVisible(false); }); useEffect(() => { @@ -166,16 +167,14 @@ export default () => { }} multiple /> - + ); }; + +export default UploadButton; diff --git a/resources/scripts/components/server/files/style.module.css b/resources/scripts/components/server/files/style.module.css index 73fb2e6ed..69c43918b 100644 --- a/resources/scripts/components/server/files/style.module.css +++ b/resources/scripts/components/server/files/style.module.css @@ -1,12 +1,14 @@ +@import '../../../assets/tailwind.css'; + .manager_actions { - @apply grid grid-cols-2 sm:grid-cols-3 w-full gap-4 mb-4; + @apply mb-4 grid w-full grid-cols-2 gap-4 sm:grid-cols-3; & button { @apply w-full first:col-span-2 sm:first:col-span-1; } @screen md { - @apply flex flex-1 justify-end mb-0; + @apply mb-0 flex flex-1 justify-end; & button { @apply w-auto; @@ -15,11 +17,11 @@ } .file_row { - @apply rounded-sm p-1 transition bg-[#ffffff08] border-[1px] border-[#ffffff07] flex items-center text-sm no-underline; + @apply flex items-center rounded-sm border-[1px] border-[#ffffff07] bg-[#ffffff08] p-1 text-sm no-underline transition; @apply hover:bg-[#ffffff12] hover:duration-0; & > .details { - @apply flex flex-1 items-center text-zinc-300 no-underline px-4 py-2 overflow-hidden truncate; + @apply flex flex-1 items-center truncate overflow-hidden px-4 py-2 text-zinc-300 no-underline; &:not(a) { @apply cursor-default; diff --git a/resources/scripts/components/server/modrinth/DisplayMods.tsx b/resources/scripts/components/server/modrinth/DisplayMods.tsx index cda714305..3c3020eb4 100644 --- a/resources/scripts/components/server/modrinth/DisplayMods.tsx +++ b/resources/scripts/components/server/modrinth/DisplayMods.tsx @@ -98,7 +98,7 @@ const ProjectSelector: React.FC = ({ appVersion, baseUrl, nonApiUrl }) => diff --git a/resources/scripts/components/server/modrinth/Dropdown.tsx b/resources/scripts/components/server/modrinth/Dropdown.tsx index 59cdd925c..1a800b7c9 100644 --- a/resources/scripts/components/server/modrinth/Dropdown.tsx +++ b/resources/scripts/components/server/modrinth/Dropdown.tsx @@ -52,7 +52,7 @@ const DropdownButton = ({ list }: Props) => { {open && ( -
+
{list.map((option) => (
{ +const ModrinthContainer = () => { const [appVersion, setAppVersion] = useState(null); const [settings, setSettings] = useState({ @@ -63,12 +63,12 @@ export default () => { return ( - + {/* TODO: Add a navbar to cycle between Downloaded, Download, and Dependency resolver*/}
@@ -107,7 +107,7 @@ export default () => {
@@ -140,3 +140,5 @@ export default () => { ); }; + +export default ModrinthContainer; diff --git a/resources/scripts/components/server/modrinth/scroll-dropdown.tsx b/resources/scripts/components/server/modrinth/scroll-dropdown.tsx index fbb530d3c..93fb06f4f 100644 --- a/resources/scripts/components/server/modrinth/scroll-dropdown.tsx +++ b/resources/scripts/components/server/modrinth/scroll-dropdown.tsx @@ -1,8 +1,11 @@ 'use client'; -import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'; import * as React from 'react'; +import HugeIconsCheck from '@/components/elements/hugeicons/Check'; +import HugeIconsChevronDown from '@/components/elements/hugeicons/ChevronDown'; +import HugeIconsChevronUp from '@/components/elements/hugeicons/ChevronUp'; + import { cn } from '@/lib/utils'; export interface ScrollItem { @@ -45,9 +48,9 @@ export function ExpandableScrollBox({ {/* Selection Button */} @@ -71,7 +74,7 @@ export function ExpandableScrollBox({ 'w-full mt-4 rounded-md overflow-hidden', 'bg-custom-medium-gray border-2 border-custom-dark-gray', 'shadow-lg transition-all duration-300 ease-in-out', - isOpen ? 'max-h-[var(--max-height)] opacity-100 my-4' : 'max-h-0 opacity-0 my-0 border-0', + isOpen ? 'max-h-(--max-height) opacity-100 my-4' : 'max-h-0 opacity-0 my-0 border-0', boxClassName, )} style={{ '--max-height': maxHeight } as React.CSSProperties} @@ -96,7 +99,7 @@ export function ExpandableScrollBox({ onClick={() => handleSelect(item)} > {item.label} - {selectedItem?.id === item.id && } + {selectedItem?.id === item.id && }
))}
diff --git a/resources/scripts/components/server/network/AllocationRow.tsx b/resources/scripts/components/server/network/AllocationRow.tsx index d30c6824d..b1aae99bf 100644 --- a/resources/scripts/components/server/network/AllocationRow.tsx +++ b/resources/scripts/components/server/network/AllocationRow.tsx @@ -1,19 +1,28 @@ import debounce from 'debounce'; -import { memo, useCallback, useState } from 'react'; +import { memo, useCallback, useEffect, useRef, useState } from 'react'; import isEqual from 'react-fast-compare'; +import { useTranslation } from 'react-i18next'; +import ActionButton from '@/components/elements/ActionButton'; import Can from '@/components/elements/Can'; import Code from '@/components/elements/Code'; import CopyOnClick from '@/components/elements/CopyOnClick'; import { Textarea } from '@/components/elements/Input'; import InputSpinner from '@/components/elements/InputSpinner'; -import { Button } from '@/components/elements/button/index'; +import Spinner from '@/components/elements/Spinner'; +import { Dialog } from '@/components/elements/dialog'; +import HugeIconsCheck from '@/components/elements/hugeicons/Check'; +import HugeIconsCopy from '@/components/elements/hugeicons/Copy'; +import HugeIconsCrown from '@/components/elements/hugeicons/Crown'; +import HugeIconsNetworkAntenna from '@/components/elements/hugeicons/NetworkAntenna'; +import HugeIconsTrash from '@/components/elements/hugeicons/Trash'; +import HugeIconsX from '@/components/elements/hugeicons/X'; import { PageListItem } from '@/components/elements/pages/PageList'; -import DeleteAllocationButton from '@/components/server/network/DeleteAllocationButton'; import { ip } from '@/lib/formatters'; import { Allocation } from '@/api/server/getServer'; +import deleteServerAllocation from '@/api/server/network/deleteServerAllocation'; import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation'; import setServerAllocationNotes from '@/api/server/network/setServerAllocationNotes'; import getServerAllocations from '@/api/swr/getServerAllocations'; @@ -27,24 +36,56 @@ interface Props { } const AllocationRow = ({ allocation }: Props) => { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [deleteLoading, setDeleteLoading] = useState(false); + const [isEditingNotes, setIsEditingNotes] = useState(false); + const [notesValue, setNotesValue] = useState(allocation.notes || ''); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const textareaRef = useRef(null); const { clearFlashes, clearAndAddHttpError } = useFlashKey('server:network'); const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); const { mutate } = getServerAllocations(); - const onNotesChanged = useCallback((id: number, notes: string) => { - mutate((data) => data?.map((a) => (a.id === id ? { ...a, notes } : a)), false); - }, []); + const onNotesChanged = useCallback( + (id: number, notes: string) => { + mutate((data) => data?.map((a) => (a.id === id ? { ...a, notes } : a)), false); + }, + [mutate], + ); - const setAllocationNotes = debounce((notes: string) => { + const saveNotes = useCallback(() => { setLoading(true); clearFlashes(); - setServerAllocationNotes(uuid, allocation.id, notes) - .then(() => onNotesChanged(allocation.id, notes)) + setServerAllocationNotes(uuid, allocation.id, notesValue) + .then(() => { + onNotesChanged(allocation.id, notesValue); + setIsEditingNotes(false); + }) .catch((error) => clearAndAddHttpError(error)) .then(() => setLoading(false)); - }, 750); + }, [uuid, allocation.id, notesValue, onNotesChanged, clearFlashes, clearAndAddHttpError]); + + const cancelEdit = useCallback(() => { + setNotesValue(allocation.notes || ''); + setIsEditingNotes(false); + }, [allocation.notes]); + + const startEdit = useCallback(() => { + setIsEditingNotes(true); + setTimeout(() => textareaRef.current?.focus(), 0); + }, []); + + useEffect(() => { + setNotesValue(allocation.notes || ''); + }, [allocation.notes]); + + // Format the full allocation string for copying + const allocationString = allocation.alias + ? `${allocation.alias}:${allocation.port}` + : `${ip(allocation.ip)}:${allocation.port}`; const setPrimaryAllocation = () => { clearFlashes(); @@ -56,62 +97,139 @@ const AllocationRow = ({ allocation }: Props) => { }); }; + const deleteAllocation = () => { + if (!confirm(t('server.network.subdomain.confirm_delete'))) return; + + clearFlashes(); + setDeleteLoading(true); + + deleteServerAllocation(uuid, allocation.id) + .then(() => { + mutate((data) => data?.filter((a) => a.id !== allocation.id), false); + }) + .catch((error) => clearAndAddHttpError(error)) + .then(() => setDeleteLoading(false)); + }; + return ( -
-
- {allocation.alias ? ( - -
- - {allocation.alias} - +
+
+
+
+ +
+
+
+ +
+

+ {allocation.alias ? allocation.alias : ip(allocation.ip)}:{allocation.port} +

+ +
+
+ {allocation.isDefault && ( + + + Primary + + )}
- - ) : ( - -
- {ip(allocation.ip)} +
+
+ + {/* Notes Section - Inline Editable */} +
+

+ {t('server.network.allocation.notes')} +

+ + {isEditingNotes ? ( +
+ + -
-
-
- -
- -
-
-
- -
- public)) ? 'checked' : '' }} id="public_1" checked>
- public)) ? '' : 'checked' }} id="public_0"> -
-
-
-
- -
-
- scheme) === 'https') ? 'checked' : '' }}> - -
-
- scheme) !== 'https') ? 'checked' : '' }}> - -
-
-

In most cases you should select to use a SSL connection. If using an IP Address or you do not wish to use SSL at all, select a HTTP connection.

-
-
- -
-
- behind_proxy) == false) ? 'checked' : '' }}> - -
-
- behind_proxy) == true) ? 'checked' : '' }}> - -
-
-

If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.

-
-
- -
-
- maintenance_mode) == false) ? 'checked' : '' }}> - -
-
- maintenance_mode) == true) ? 'checked' : '' }}> - -
-
-

If the node is marked as 'Under Maintenance' users won't be able to access servers that are on this node.

-
-
-
+
+
+
+

Settings

+
+
+
+ +
+ +

Character limits: a-zA-Z0-9_.- and [Space] (min 1, + max 100 characters).

-
-
-
-

Allocation Limits

-
-
-
-
-
- -
- - MiB -
-
-
- -
- - % -
-
-
-

Enter the total amount of memory available on this node for allocation to servers. You may also provide a percentage that can allow allocation of more than the defined memory.

-
-
-
-
- -
- - MiB -
-
-
- -
- - % -
-
-
-

Enter the total amount of disk space available on this node for server allocation. You may also provide a percentage that will determine the amount of disk space over the set limit to allow.

-
-
-
-
-
-
-

General Configuration

-
-
-
- -
- - MiB -
-

Enter the maximum size of files that can be uploaded through the web-based file manager.

-
-
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-

The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physical server's SSH process.

-
-
-
-
-
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ public)) ? 'checked' : '' }} + id="public_1" checked>
+ public)) ? '' : 'checked' }} + id="public_0"> +
+
+
+ +
+ +
+

+ + Domain name that browsers will use to connect to Wings (e.g wings.example.com). + An IP address may be used only if you are not using SSL for this node. + Why? + +

+
+
+ +
+ +
+

+ + Optional: + Leave blank to use the Public FQDN for panel-to-Wings communication. + If specified, this internal domain name will be used for panel-to-Wings communication instead + (e.g wings-internal.example.com or 10.0.0.5). + Useful for internal networks where the panel needs to communicate with Wings using a + different address than what browsers use. + +

+
+
+ +
+
+ scheme) === 'https') ? 'checked' : '' }}> + +
+
+ scheme) !== 'https') ? 'checked' : '' }}> + +
+
+

In most cases you should select to use a SSL connection. If using an IP Address + or you do not wish to use SSL at all, select a HTTP connection.

+
+
+ +
+
+ behind_proxy) == false) ? 'checked' : '' }}> + +
+
+ behind_proxy) == true) ? 'checked' : '' }}> + +
+
+

If you are running the daemon behind a proxy such as Cloudflare, select this to + have the daemon skip looking for certificates on boot.

+
+
+ +
+
+ maintenance_mode) == false) ? 'checked' : '' }}> + +
+
+ maintenance_mode) == true) ? 'checked' : '' }}> + +
+
+

If the node is marked as 'Under Maintenance' users won't be able to access + servers that are on this node.

+
+
+
+
+
+
+
+

Allocation Limits

+
+
+
+
+
+ +
+ + MiB +
+
+
+ +
+ + % +
+
+
+

Enter the total amount of memory available on this node for allocation to + servers. You may also provide a percentage that can allow allocation of more than the defined memory.

-
-
-

Save Settings

-
-
-
-
- -
-

Resetting the daemon master key will void any request coming from the old key. This key is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this key regularly for security.

-
-
- -
+
+
+ +
+ + MiB +
+
+
+ +
+ + % +
+
+

Enter the total amount of disk space available on this node for server + allocation. You may also provide a percentage that will determine the amount of disk space over the set + limit to allow.

+
+
+
- +
+
+
+

General Configuration

+
+
+
+ +
+ + MiB +
+

Enter the maximum size of files that can be uploaded through the web-based file + manager.

+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+

The daemon runs its own SFTP management container and does not use the SSHd + process on the main physical server. Do not use the same port that you have assigned for + your physical server's SSH process.

+
+
+
+
+
+
+
+
+
+

Save Settings

+
+
+
+
+ +
+

Resetting the daemon master key will void any request coming from the old key. + This key is used for all sensitive operations on the daemon including server creation and deletion. We + suggest changing this key regularly for security.

+
+
+ +
+
+
+ @endsection @section('footer-scripts') - @parent - -@endsection + +@endsection \ No newline at end of file diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php index 4d5ebcdcd..4c475baf1 100644 --- a/resources/views/admin/servers/index.blade.php +++ b/resources/views/admin/servers/index.blade.php @@ -46,7 +46,7 @@ {{ $server->name }} {{ $server->uuid }} - {{ $server->user->username }} + {{ $server->user->username }} ({{ $server->user->email }}) {{ $server->node->name }} {{ $server->allocation->alias }}:{{ $server->allocation->port }} @@ -59,6 +59,10 @@ @else Active @endif + + @if($server->exclude_from_resource_calculation) +
Excluded + @endif diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 4198634f1..67afa33f1 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -176,6 +176,19 @@

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

+
+ + +
+ + MiB +
+ +

Additional memory allocated to the container that doesn't go to the SERVER_MEMORY variable. Setting to 0 disables overhead memory.

+
+
+ +
@@ -217,6 +230,14 @@

Terminates the server if it breaches the memory limits. Enabling OOM killer may cause server processes to exit unexpectedly.

+
+
+ + +
+ +

When enabled, this server will not be included in resource calculations when provisioning new servers onto this node. Useful for testing or development servers.

+
diff --git a/resources/views/admin/servers/view/build.blade.php b/resources/views/admin/servers/view/build.blade.php index 655ea36af..be2b31a7f 100644 --- a/resources/views/admin/servers/view/build.blade.php +++ b/resources/views/admin/servers/view/build.blade.php @@ -47,6 +47,14 @@

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

+
+ +
+ + MiB +
+

Additional memory allocated to the container that doesn't go to the SERVER_MEMORY variable. Setting to 0 disables overhead memory.

+
@@ -86,6 +94,22 @@

+
+ +
+
+ exclude_from_resource_calculation)checked @endif> + +
+
+ exclude_from_resource_calculation)checked @endif> + +
+

+ When enabled, this server will not be included in resource calculations when provisioning new servers onto this node. Useful for testing or development servers. +

+
+
diff --git a/resources/views/admin/servers/view/index.blade.php b/resources/views/admin/servers/view/index.blade.php index b9e9cf7b9..a6955bceb 100644 --- a/resources/views/admin/servers/view/index.blade.php +++ b/resources/views/admin/servers/view/index.blade.php @@ -150,6 +150,7 @@

{{ str_limit($server->user->username, 16) }}

+

{{ $server->user->email }}

Server Owner

diff --git a/resources/views/admin/settings/advanced.blade.php b/resources/views/admin/settings/advanced.blade.php index 4c3428bc3..fb9cf4115 100644 --- a/resources/views/admin/settings/advanced.blade.php +++ b/resources/views/admin/settings/advanced.blade.php @@ -18,54 +18,6 @@
-
-
-

reCAPTCHA

-
-
-
-
- -
- -

If enabled, login forms and password reset forms will do a silent captcha - check and display a visible captcha if needed.

-
-
-
- -
- -
-
-
- -
- -

Used for communication between your site and Google. Be sure to keep it a - secret.

-
-
-
- @if($showRecaptchaWarning) -
-
-
- You are currently using reCAPTCHA keys that were shipped with this Panel. For improved security it is - recommended to generate new invisible reCAPTCHA - keys that tied specifically to your website. -
-
-
- @endif -
-

HTTP Connections

diff --git a/resources/views/admin/settings/captcha.blade.php b/resources/views/admin/settings/captcha.blade.php index 1c37794d1..0148076a4 100644 --- a/resources/views/admin/settings/captcha.blade.php +++ b/resources/views/admin/settings/captcha.blade.php @@ -6,7 +6,7 @@ @endsection @section('content-header') -

Captcha SettingsConfigure captcha settings for Pyrodactyl.

+

Captcha SettingsConfigure captcha protection for authentication forms.