From 3da6c2832c73ac45192941282d52f4ad8f0170e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Tue, 14 Apr 2026 11:39:46 +0200 Subject: [PATCH] first commit --- .dockerignore | 11 + .env.example | 9 + .gitignore | 36 + README.md | 18 + backend/.env.example | 4 + backend/Dockerfile | 16 + backend/app/__init__.py | 0 backend/app/api/__init__.py | 12 + backend/app/api/deps.py | 40 + backend/app/api/routes/auth.py | 106 + backend/app/api/routes/backups.py | 128 + backend/app/api/routes/dashboard.py | 42 + backend/app/api/routes/health.py | 19 + backend/app/api/routes/logs.py | 32 + backend/app/api/routes/routers.py | 120 + backend/app/api/routes/settings.py | 78 + backend/app/api/routes/swos_beta.py | 33 + backend/app/core/config.py | 38 + backend/app/core/cron_utils.py | 88 + backend/app/core/security.py | 22 + backend/app/db/base.py | 7 + backend/app/db/session.py | 84 + backend/app/main.py | 30 + backend/app/models/backup.py | 19 + backend/app/models/log.py | 12 + backend/app/models/router.py | 34 + backend/app/models/settings.py | 28 + backend/app/models/user.py | 15 + backend/app/schemas/auth.py | 31 + backend/app/schemas/backup.py | 50 + backend/app/schemas/dashboard.py | 28 + backend/app/schemas/router.py | 104 + backend/app/schemas/settings.py | 88 + backend/app/schemas/swos_beta.py | 33 + backend/app/services/backup_service.py | 350 + backend/app/services/file_service.py | 38 + backend/app/services/log_service.py | 24 + backend/app/services/notification_service.py | 78 + backend/app/services/router_service.py | 220 + backend/app/services/scheduler.py | 249 + backend/app/services/settings_service.py | 32 + backend/app/services/swos_beta_service.py | 174 + backend/requirements.txt | 14 + backend/scripts/migrate_legacy_sqlite.py | 117 + backend/tests/test_auth.py | 35 + backend/tests/test_health.py | 10 + ...test_routeros_logging_and_files_filters.py | 125 + backend/tests/test_routers.py | 62 + backend/tests/test_scheduler.py | 24 + docker-compose.yml | 38 + env.example | 9 + frontend/Dockerfile | 12 + frontend/angular.json | 75 + frontend/nginx/default.conf | 14 + frontend/package-lock.json | 15080 ++++++++++++++++ frontend/package.json | 41 + frontend/proxy.conf.json | 8 + frontend/src/app/app.component.html | 68 + frontend/src/app/app.component.ts | 141 + frontend/src/app/app.routes.ts | 29 + frontend/src/app/core/guards/auth.guard.ts | 14 + frontend/src/app/core/guards/guest.guard.ts | 15 + .../app/core/interceptors/auth.interceptor.ts | 7 + .../app/core/services/api-status.service.ts | 83 + frontend/src/app/core/services/api.service.ts | 9 + .../src/app/core/services/auth.service.ts | 88 + .../src/app/core/services/font.service.ts | 56 + .../src/app/core/services/language.service.ts | 69 + .../src/app/core/services/layout.service.ts | 22 + .../src/app/core/services/theme.service.ts | 41 + frontend/src/app/core/services/ui.service.ts | 81 + frontend/src/app/core/theme-preset.ts | 134 + .../auth/change-password-page.component.html | 50 + .../auth/change-password-page.component.ts | 90 + .../features/auth/login-page.component.html | 29 + .../app/features/auth/login-page.component.ts | 49 + .../auth/register-page.component.html | 28 + .../features/auth/register-page.component.ts | 59 + .../dashboard/dashboard-page.component.html | 107 + .../dashboard/dashboard-page.component.ts | 413 + .../diff-configs-page.component.html | 143 + .../diff-configs-page.component.ts | 248 + .../features/files/files-page.component.html | 191 + .../features/files/files-page.component.ts | 457 + .../features/logs/logs-page.component.html | 25 + .../app/features/logs/logs-page.component.ts | 63 + .../routers/router-detail-page.component.html | 170 + .../routers/router-detail-page.component.ts | 346 + .../routers/routers-page.component.html | 156 + .../routers/routers-page.component.ts | 217 + .../settings/settings-page.component.html | 327 + .../settings/settings-page.component.ts | 499 + .../swos-beta/swos-beta-page.component.html | 80 + .../swos-beta/swos-beta-page.component.ts | 131 + .../shared/auth/auth-toolbar.component.html | 22 + .../app/shared/auth/auth-toolbar.component.ts | 28 + .../shared/layout/app-sidebar.component.html | 27 + .../shared/layout/app-sidebar.component.ts | 16 + .../shared/layout/app-topbar.component.html | 42 + .../app/shared/layout/app-topbar.component.ts | 42 + .../app/shared/ui/page-header.component.html | 10 + .../app/shared/ui/page-header.component.ts | 14 + .../app/shared/ui/section-card.component.html | 12 + .../app/shared/ui/section-card.component.ts | 14 + .../app/shared/ui/stat-card.component.html | 13 + .../src/app/shared/ui/stat-card.component.ts | 24 + frontend/src/assets/i18n/en.json | 541 + frontend/src/assets/i18n/es.json | 541 + frontend/src/assets/i18n/no.json | 541 + frontend/src/assets/i18n/pl.json | 541 + frontend/src/favicon.ico | 0 frontend/src/index.html | 22 + frontend/src/main.ts | 40 + frontend/src/styles.css | 4 + frontend/src/styles/auth.css | 165 + frontend/src/styles/dashboard.css | 187 + frontend/src/styles/layout.css | 195 + frontend/src/styles/pages.css | 3817 ++++ frontend/tsconfig.app.json | 9 + frontend/tsconfig.json | 21 + make_zip.py | 70 + reverse-proxy/Dockerfile | 4 + reverse-proxy/default.conf | 60 + start_dev.sh | 95 + start_prod.sh | 15 + 125 files changed, 30111 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 backend/.env.example create mode 100644 backend/Dockerfile create mode 100644 backend/app/__init__.py create mode 100644 backend/app/api/__init__.py create mode 100644 backend/app/api/deps.py create mode 100644 backend/app/api/routes/auth.py create mode 100644 backend/app/api/routes/backups.py create mode 100644 backend/app/api/routes/dashboard.py create mode 100644 backend/app/api/routes/health.py create mode 100644 backend/app/api/routes/logs.py create mode 100644 backend/app/api/routes/routers.py create mode 100644 backend/app/api/routes/settings.py create mode 100644 backend/app/api/routes/swos_beta.py create mode 100644 backend/app/core/config.py create mode 100644 backend/app/core/cron_utils.py create mode 100644 backend/app/core/security.py create mode 100644 backend/app/db/base.py create mode 100644 backend/app/db/session.py create mode 100644 backend/app/main.py create mode 100644 backend/app/models/backup.py create mode 100644 backend/app/models/log.py create mode 100644 backend/app/models/router.py create mode 100644 backend/app/models/settings.py create mode 100644 backend/app/models/user.py create mode 100644 backend/app/schemas/auth.py create mode 100644 backend/app/schemas/backup.py create mode 100644 backend/app/schemas/dashboard.py create mode 100644 backend/app/schemas/router.py create mode 100644 backend/app/schemas/settings.py create mode 100644 backend/app/schemas/swos_beta.py create mode 100644 backend/app/services/backup_service.py create mode 100644 backend/app/services/file_service.py create mode 100644 backend/app/services/log_service.py create mode 100644 backend/app/services/notification_service.py create mode 100644 backend/app/services/router_service.py create mode 100644 backend/app/services/scheduler.py create mode 100644 backend/app/services/settings_service.py create mode 100644 backend/app/services/swos_beta_service.py create mode 100644 backend/requirements.txt create mode 100755 backend/scripts/migrate_legacy_sqlite.py create mode 100644 backend/tests/test_auth.py create mode 100644 backend/tests/test_health.py create mode 100644 backend/tests/test_routeros_logging_and_files_filters.py create mode 100644 backend/tests/test_routers.py create mode 100644 backend/tests/test_scheduler.py create mode 100644 docker-compose.yml create mode 100644 env.example create mode 100644 frontend/Dockerfile create mode 100644 frontend/angular.json create mode 100644 frontend/nginx/default.conf create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/proxy.conf.json create mode 100644 frontend/src/app/app.component.html create mode 100644 frontend/src/app/app.component.ts create mode 100644 frontend/src/app/app.routes.ts create mode 100644 frontend/src/app/core/guards/auth.guard.ts create mode 100644 frontend/src/app/core/guards/guest.guard.ts create mode 100644 frontend/src/app/core/interceptors/auth.interceptor.ts create mode 100644 frontend/src/app/core/services/api-status.service.ts create mode 100644 frontend/src/app/core/services/api.service.ts create mode 100644 frontend/src/app/core/services/auth.service.ts create mode 100644 frontend/src/app/core/services/font.service.ts create mode 100644 frontend/src/app/core/services/language.service.ts create mode 100644 frontend/src/app/core/services/layout.service.ts create mode 100644 frontend/src/app/core/services/theme.service.ts create mode 100644 frontend/src/app/core/services/ui.service.ts create mode 100644 frontend/src/app/core/theme-preset.ts create mode 100644 frontend/src/app/features/auth/change-password-page.component.html create mode 100644 frontend/src/app/features/auth/change-password-page.component.ts create mode 100644 frontend/src/app/features/auth/login-page.component.html create mode 100644 frontend/src/app/features/auth/login-page.component.ts create mode 100644 frontend/src/app/features/auth/register-page.component.html create mode 100644 frontend/src/app/features/auth/register-page.component.ts create mode 100644 frontend/src/app/features/dashboard/dashboard-page.component.html create mode 100644 frontend/src/app/features/dashboard/dashboard-page.component.ts create mode 100644 frontend/src/app/features/diff-configs/diff-configs-page.component.html create mode 100644 frontend/src/app/features/diff-configs/diff-configs-page.component.ts create mode 100644 frontend/src/app/features/files/files-page.component.html create mode 100644 frontend/src/app/features/files/files-page.component.ts create mode 100644 frontend/src/app/features/logs/logs-page.component.html create mode 100644 frontend/src/app/features/logs/logs-page.component.ts create mode 100644 frontend/src/app/features/routers/router-detail-page.component.html create mode 100644 frontend/src/app/features/routers/router-detail-page.component.ts create mode 100644 frontend/src/app/features/routers/routers-page.component.html create mode 100644 frontend/src/app/features/routers/routers-page.component.ts create mode 100644 frontend/src/app/features/settings/settings-page.component.html create mode 100644 frontend/src/app/features/settings/settings-page.component.ts create mode 100644 frontend/src/app/features/swos-beta/swos-beta-page.component.html create mode 100644 frontend/src/app/features/swos-beta/swos-beta-page.component.ts create mode 100644 frontend/src/app/shared/auth/auth-toolbar.component.html create mode 100644 frontend/src/app/shared/auth/auth-toolbar.component.ts create mode 100644 frontend/src/app/shared/layout/app-sidebar.component.html create mode 100644 frontend/src/app/shared/layout/app-sidebar.component.ts create mode 100644 frontend/src/app/shared/layout/app-topbar.component.html create mode 100644 frontend/src/app/shared/layout/app-topbar.component.ts create mode 100644 frontend/src/app/shared/ui/page-header.component.html create mode 100644 frontend/src/app/shared/ui/page-header.component.ts create mode 100644 frontend/src/app/shared/ui/section-card.component.html create mode 100644 frontend/src/app/shared/ui/section-card.component.ts create mode 100644 frontend/src/app/shared/ui/stat-card.component.html create mode 100644 frontend/src/app/shared/ui/stat-card.component.ts create mode 100644 frontend/src/assets/i18n/en.json create mode 100644 frontend/src/assets/i18n/es.json create mode 100644 frontend/src/assets/i18n/no.json create mode 100644 frontend/src/assets/i18n/pl.json create mode 100644 frontend/src/favicon.ico create mode 100644 frontend/src/index.html create mode 100644 frontend/src/main.ts create mode 100644 frontend/src/styles.css create mode 100644 frontend/src/styles/auth.css create mode 100644 frontend/src/styles/dashboard.css create mode 100644 frontend/src/styles/layout.css create mode 100644 frontend/src/styles/pages.css create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 make_zip.py create mode 100644 reverse-proxy/Dockerfile create mode 100644 reverse-proxy/default.conf create mode 100755 start_dev.sh create mode 100755 start_prod.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dbce0f0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.gitignore +**/__pycache__ +**/.pytest_cache +**/.mypy_cache +**/.ruff_cache +**/.venv +**/node_modules +**/dist +**/.angular +.env diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b1167d8 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +SECRET_KEY=change-me-before-production +ACCESS_TOKEN_EXPIRE_MINUTES=7899999 +ALLOW_REGISTRATION=true +DEFAULT_ADMIN_USERNAME=admin +DEFAULT_ADMIN_PASSWORD=admin +APP_PORT=5581 +API_PREFIX=/api +DATA_DIR=/app/storage +DATABASE_URL=sqlite:////app/storage/db/routeros_backup_next.db \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f06ed8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# Python +__pycache__/ +*.py[cod] +*.so +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.venv/ +venv/ +.env +.env.* +!.env.example + +# Backend runtime +backend/storage/routeros_backup_next.db +backend/data/ +backend/storage/ +data/ +*.log + +# Node / Angular +frontend/node_modules/ +frontend/dist/ +frontend/.angular/ +frontend/.cache/ + +# OS / editors +.DS_Store +.idea/ +.vscode/ + +*.zip +docker-data/db/* +docker-data/*.rsc +docker-data/*.backup +storage/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..9765b99 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Mikrotik Backup System + +## Deploy in docker +```bash +cp .env.example .env +``` + +edit SECRET_KEY & DEFAULT_ADMIN_PASSWORD +and run: + +`bash start_prod.sh` + +or + +`docker compose up -d` + +## Default app port: +`http://127.0.0.1:5581` diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..003f07a --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,4 @@ +APP_PORT=8080 +API_PREFIX=/api +DATA_DIR=/app/storage +DATABASE_URL=sqlite:////app/storage/routeros_backup_next.db \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..7ba22c2 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.14-slim + +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends curl build-essential && rm -rf /var/lib/apt/lists/* + +COPY backend/requirements.txt /app/requirements.txt +RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r /app/requirements.txt + +COPY backend /app +RUN mkdir -p /app/storage + +EXPOSE 8000 +CMD ["fastapi", "run", "app/main.py", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 0000000..1d01bfa --- /dev/null +++ b/backend/app/api/__init__.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter + +from app.api.routes import auth, backups, dashboard, health, logs, routers, settings + +api_router = APIRouter() +api_router.include_router(auth.router, prefix='/auth', tags=['auth']) +api_router.include_router(dashboard.router, prefix='/dashboard', tags=['dashboard']) +api_router.include_router(routers.router, prefix='/routers', tags=['routers']) +api_router.include_router(backups.router, prefix='/backups', tags=['backups']) +api_router.include_router(settings.router, prefix='/settings', tags=['settings']) +api_router.include_router(logs.router, prefix='/logs', tags=['logs']) +api_router.include_router(health.router, tags=['health']) diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py new file mode 100644 index 0000000..694e674 --- /dev/null +++ b/backend/app/api/deps.py @@ -0,0 +1,40 @@ +from typing import Generator + +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from jose import JWTError, jwt +from sqlalchemy.orm import Session + +from app.core.config import settings +from app.db.session import SessionLocal +from app.models.user import User + + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") + + +def get_db() -> Generator[Session, None, None]: + db = SessionLocal() + try: + yield db + finally: + db.close() + + +def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> User: + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, settings.secret_key, algorithms=[settings.jwt_algorithm]) + username: str | None = payload.get("sub") + if username is None: + raise credentials_exception + except JWTError as exc: + raise credentials_exception from exc + user = db.query(User).filter(User.username == username).first() + if not user: + raise credentials_exception + return user diff --git a/backend/app/api/routes/auth.py b/backend/app/api/routes/auth.py new file mode 100644 index 0000000..2b46804 --- /dev/null +++ b/backend/app/api/routes/auth.py @@ -0,0 +1,106 @@ +from datetime import timedelta + +from fastapi import APIRouter, Depends, HTTPException, Request, status +from sqlalchemy.orm import Session + +from app.api.deps import get_current_user, get_db +from app.core.config import settings +from app.core.security import create_access_token, get_password_hash, verify_password +from app.models.user import User +from app.schemas.auth import ( + ChangePasswordRequest, + RegisterRequest, + TokenResponse, + UpdateUserPreferencesRequest, + UserResponse, +) + +router = APIRouter() + + +@router.post("/register", response_model=UserResponse) +def register(payload: RegisterRequest, db: Session = Depends(get_db)): + if not settings.allow_registration: + raise HTTPException(status_code=403, detail="Registration is disabled") + existing = db.query(User).filter(User.username == payload.username).first() + if existing: + raise HTTPException(status_code=409, detail="Username already exists") + user = User(username=payload.username, password_hash=get_password_hash(payload.password)) + db.add(user) + db.commit() + db.refresh(user) + return user + + +@router.post("/login", response_model=TokenResponse) +async def login(request: Request, db: Session = Depends(get_db)): + username = None + password = None + content_type = (request.headers.get("content-type") or "").lower() + + if "application/json" in content_type: + try: + payload = await request.json() + except Exception: + payload = {} + username = payload.get("username") + password = payload.get("password") + else: + form_data = await request.form() + username = form_data.get("username") + password = form_data.get("password") + + if not username or not password: + raise HTTPException(status_code=422, detail="Username and password are required") + + user = db.query(User).filter(User.username == username).first() + if not user or not verify_password(password, user.password_hash): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials") + token = create_access_token( + subject=user.username, + expires_delta=timedelta(minutes=settings.access_token_expire_minutes), + ) + return TokenResponse(access_token=token, user=UserResponse.model_validate(user)) + + +@router.get("/me", response_model=UserResponse) +def me(current_user: User = Depends(get_current_user)): + return current_user + + + + +@router.put("/preferences", response_model=UserResponse) +def update_preferences( + payload: UpdateUserPreferencesRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db), +): + preferred_language = (payload.preferred_language or 'pl').strip().lower() + preferred_font = (payload.preferred_font or 'default').strip().lower() + + if preferred_language not in {'pl', 'en', 'es', 'no'}: + raise HTTPException(status_code=422, detail='Unsupported language') + if preferred_font not in {'default', 'adwaita_mono', 'hack'}: + raise HTTPException(status_code=422, detail='Unsupported font') + + current_user.preferred_language = preferred_language + current_user.preferred_font = preferred_font + db.add(current_user) + db.commit() + db.refresh(current_user) + return current_user + + +@router.post("/change-password") +def change_password( + payload: ChangePasswordRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db), +): + if not verify_password(payload.current_password, current_user.password_hash): + raise HTTPException(status_code=400, detail="Current password is invalid") + current_user.password_hash = get_password_hash(payload.new_password) + db.add(current_user) + db.commit() + return {"message": "Password changed successfully"} diff --git a/backend/app/api/routes/backups.py b/backend/app/api/routes/backups.py new file mode 100644 index 0000000..991bb2c --- /dev/null +++ b/backend/app/api/routes/backups.py @@ -0,0 +1,128 @@ +from datetime import date +import io +import zipfile + +from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse +from sqlalchemy.orm import Session + +from app.api.deps import get_current_user, get_db +from app.models.user import User +from app.schemas.backup import BackupDiffResponse, BackupResponse, BulkActionRequest +from app.services.backup_service import backup_service + +router = APIRouter() + + +@router.get("", response_model=list[BackupResponse]) +def list_backups( + search: str | None = Query(default=None), + backup_type: str | None = Query(default=None, pattern="^(export|binary)$"), + router_id: int | None = Query(default=None), + created_on: date | None = Query(default=None), + sort_by: str = Query(default="created_at"), + order: str = Query(default="desc", pattern="^(asc|desc)$"), + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db), +): + return backup_service.list_backups( + db, + current_user, + search=search, + backup_type=backup_type, + router_id=router_id, + created_on=created_on, + sort_by=sort_by, + order=order, + ) + + +@router.get("/router/{router_id}", response_model=list[BackupResponse]) +def list_router_backups(router_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + return backup_service.list_router_backups(db, current_user, router_id) + + +@router.post("/routers/export-all") +def export_all(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + return backup_service.export_all(db, current_user) + + +@router.post("/routers/binary-all") +def binary_all(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + return backup_service.binary_all(db, current_user) + + +@router.post("/router/{router_id}/export", response_model=BackupResponse) +def export_router(router_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + return backup_service.export_router(db, current_user, router_id) + + +@router.post("/router/{router_id}/binary", response_model=BackupResponse) +def binary_backup(router_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + return backup_service.binary_backup(db, current_user, router_id) + + +@router.post("/router/{router_id}/upload/{backup_id}") +def upload_to_router(router_id: int, backup_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + backup_service.upload_backup_to_router(db, current_user, router_id, backup_id) + return {"message": "Backup uploaded to router"} + + +@router.delete("/{backup_id}") +def delete_backup(backup_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + backup_service.delete_backup(db, current_user, backup_id) + return {"message": "Backup deleted"} + + +@router.get("/{backup_id}/download") +def download_backup(backup_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + backup = backup_service.get_backup_for_user(db, current_user, backup_id) + return FileResponse(path=backup.file_path, filename=backup.file_name) + + +@router.get("/{backup_id}/view") +def view_export(backup_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + backup = backup_service.get_backup_for_user(db, current_user, backup_id) + if backup.backup_type != "export": + raise HTTPException(status_code=400, detail="Only export backups can be viewed") + with open(backup.file_path, "r", encoding="utf-8", errors="ignore") as handle: + return {"content": handle.read(), "backup": BackupResponse.model_validate(backup_service._serialize_backup(backup))} + + +@router.post("/{backup_id}/email") +def email_backup(backup_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + backup_service.email_backup(db, current_user, backup_id) + return {"message": "Backup sent by email"} + + +@router.get("/{left_id}/diff/{right_id}", response_model=BackupDiffResponse) +def diff_backups(left_id: int, right_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + return backup_service.diff_backups(db, current_user, left_id, right_id) + + +@router.get("/{left_id}/diff/{right_id}/html", response_class=HTMLResponse) +def diff_backups_html(left_id: int, right_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + result = backup_service.diff_backups(db, current_user, left_id, right_id) + return HTMLResponse(result["diff_html"]) + + +@router.post("/bulk") +def bulk_action(payload: BulkActionRequest, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + backups = [backup_service.get_backup_for_user(db, current_user, backup_id) for backup_id in payload.backup_ids] + if payload.action == "delete": + for backup in backups: + backup_service.delete_backup(db, current_user, backup.id, commit=False) + db.commit() + return {"message": f"Deleted {len(backups)} backups"} + if payload.action == "download": + stream = io.BytesIO() + with zipfile.ZipFile(stream, "w") as archive: + for backup in backups: + archive.write(backup.file_path, backup.file_name) + stream.seek(0) + return StreamingResponse( + stream, + media_type="application/zip", + headers={"Content-Disposition": 'attachment; filename="backups.zip"'}, + ) + raise HTTPException(status_code=400, detail="Unsupported bulk action") diff --git a/backend/app/api/routes/dashboard.py b/backend/app/api/routes/dashboard.py new file mode 100644 index 0000000..d16fb2e --- /dev/null +++ b/backend/app/api/routes/dashboard.py @@ -0,0 +1,42 @@ +from fastapi import APIRouter, Depends +from sqlalchemy import func +from sqlalchemy.orm import Session + +from app.api.deps import get_current_user, get_db +from app.models.backup import Backup +from app.models.log import OperationLog +from app.models.router import Router +from app.models.user import User +from app.schemas.dashboard import DashboardResponse +from app.services.file_service import get_storage_stats + +router = APIRouter() + + +@router.get("", response_model=DashboardResponse) +def get_dashboard(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + routers_count = db.query(func.count(Router.id)).filter(Router.owner_id == current_user.id).scalar() or 0 + export_count = ( + db.query(func.count(Backup.id)) + .join(Router) + .filter(Router.owner_id == current_user.id, Backup.backup_type == "export") + .scalar() + or 0 + ) + binary_count = ( + db.query(func.count(Backup.id)) + .join(Router) + .filter(Router.owner_id == current_user.id, Backup.backup_type == "binary") + .scalar() + or 0 + ) + recent_logs = db.query(OperationLog).order_by(OperationLog.timestamp.desc()).limit(10).all() + storage = get_storage_stats() + return DashboardResponse( + routers_count=routers_count, + export_count=export_count, + binary_count=binary_count, + total_backups=export_count + binary_count, + storage=storage, + recent_logs=recent_logs, + ) diff --git a/backend/app/api/routes/health.py b/backend/app/api/routes/health.py new file mode 100644 index 0000000..4390514 --- /dev/null +++ b/backend/app/api/routes/health.py @@ -0,0 +1,19 @@ +from datetime import datetime, timezone + +from fastapi import APIRouter, Depends +from sqlalchemy import text +from sqlalchemy.orm import Session + +from app.api.deps import get_db + +router = APIRouter() + + +@router.get("/health") +def health(db: Session = Depends(get_db)): + status = "ok" + try: + db.execute(text("SELECT 1")) + except Exception: + status = "error" + return {"status": status, "timestamp": datetime.now(timezone.utc).isoformat()} diff --git a/backend/app/api/routes/logs.py b/backend/app/api/routes/logs.py new file mode 100644 index 0000000..4814620 --- /dev/null +++ b/backend/app/api/routes/logs.py @@ -0,0 +1,32 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.orm import Session + +from app.api.deps import get_current_user, get_db +from app.models.log import OperationLog +from app.models.user import User +from app.services.log_service import log_service + +router = APIRouter() + + +@router.get("") +def get_logs( + limit: int = Query(100, ge=1, le=500), + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db), +): + _ = current_user + return db.query(OperationLog).order_by(OperationLog.timestamp.desc()).limit(limit).all() + + +@router.delete("/older-than/{days}") +def delete_logs( + days: int, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db), +): + _ = current_user + if days < 1: + raise HTTPException(status_code=400, detail="Days must be >= 1") + deleted = log_service.delete_older_than(db, days) + return {"message": f"Deleted {deleted} logs", "deleted": deleted} diff --git a/backend/app/api/routes/routers.py b/backend/app/api/routes/routers.py new file mode 100644 index 0000000..fa58cfc --- /dev/null +++ b/backend/app/api/routes/routers.py @@ -0,0 +1,120 @@ +from pathlib import Path + +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app.api.deps import get_current_user, get_db +from app.models.router import Router +from app.models.user import User +from app.schemas.router import RouterCreate, RouterResponse, RouterTestConnection, RouterUpdate +from app.services.router_service import router_service +from app.services.settings_service import settings_service + +router = APIRouter() + + +def serialize_router(router: Router, global_settings) -> RouterResponse: + has_router_key = bool((router.ssh_key or '').strip()) + has_global_key = bool((global_settings.global_ssh_key or '').strip()) + router_user = (router.ssh_user or '').strip() or None + router_password = (router.ssh_password or '').strip() or None + default_swos_user = (global_settings.default_switchos_username or '').strip() or None + default_swos_password = (global_settings.default_switchos_password or '').strip() or None + effective_username = router_user + uses_global_switchos_credentials = False + has_effective_password = bool(router_password) + + if router.device_type == 'switchos': + effective_username = router_user or default_swos_user + uses_global_switchos_credentials = bool( + (not router_user and default_swos_user) or (not router_password and default_swos_password) + ) + has_effective_password = bool(router_password or default_swos_password) + + payload = RouterResponse.model_validate(router, from_attributes=True).model_dump() + payload['effective_username'] = effective_username + payload['uses_global_ssh_key'] = router.device_type == 'routeros' and has_global_key and not has_router_key + payload['has_effective_ssh_key'] = router.device_type == 'routeros' and (has_router_key or has_global_key) + payload['uses_global_switchos_credentials'] = uses_global_switchos_credentials + payload['has_effective_password'] = has_effective_password + payload['supports_export'] = router.device_type == 'routeros' + payload['supports_restore_upload'] = router.device_type == 'routeros' + return RouterResponse.model_validate(payload) + + +@router.get('', response_model=list[RouterResponse]) +def list_routers(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + global_settings = settings_service.get_or_create(db) + routers = db.query(Router).filter(Router.owner_id == current_user.id).order_by(Router.created_at.desc()).all() + return [serialize_router(router, global_settings) for router in routers] + + +@router.post('', response_model=RouterResponse) +def create_router(payload: RouterCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + router_data = payload.model_dump() + if router_data.get('device_type') == 'switchos' and router_data.get('ssh_user') is None: + router_data['ssh_user'] = '' + router = Router(**router_data, owner_id=current_user.id) + db.add(router) + db.commit() + db.refresh(router) + global_settings = settings_service.get_or_create(db) + return serialize_router(router, global_settings) + + +@router.get('/{router_id}', response_model=RouterResponse) +def get_router(router_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + router = db.query(Router).filter(Router.id == router_id, Router.owner_id == current_user.id).first() + if not router: + raise HTTPException(status_code=404, detail='Device not found') + global_settings = settings_service.get_or_create(db) + return serialize_router(router, global_settings) + + +@router.put('/{router_id}', response_model=RouterResponse) +def update_router(router_id: int, payload: RouterUpdate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + router = db.query(Router).filter(Router.id == router_id, Router.owner_id == current_user.id).first() + if not router: + raise HTTPException(status_code=404, detail='Device not found') + changes = payload.model_dump(exclude_unset=True) + target_device_type = changes.get('device_type', router.device_type) + if target_device_type == 'switchos': + changes['ssh_key'] = None + if 'port' not in changes: + changes['port'] = 80 + if changes.get('ssh_user') is None: + changes['ssh_user'] = '' + elif target_device_type == 'routeros' and 'port' not in changes and router.device_type != 'routeros': + changes['port'] = 22 + if not changes.get('ssh_user'): + changes['ssh_user'] = router.ssh_user or 'admin' + for key, value in changes.items(): + setattr(router, key, value) + db.add(router) + db.commit() + db.refresh(router) + global_settings = settings_service.get_or_create(db) + return serialize_router(router, global_settings) + + +@router.delete('/{router_id}') +def delete_router(router_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + router = db.query(Router).filter(Router.id == router_id, Router.owner_id == current_user.id).first() + if not router: + raise HTTPException(status_code=404, detail='Device not found') + for backup in list(router.backups): + path = Path(backup.file_path) + if path.exists(): + path.unlink() + db.delete(router) + db.commit() + return {'message': 'Device deleted'} + + +@router.get('/{router_id}/test-connection', response_model=RouterTestConnection) +def test_connection(router_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + router = db.query(Router).filter(Router.id == router_id, Router.owner_id == current_user.id).first() + if not router: + raise HTTPException(status_code=404, detail='Device not found') + global_settings = settings_service.get_or_create(db) + return router_service.test_connection(db, router, global_settings) diff --git a/backend/app/api/routes/settings.py b/backend/app/api/routes/settings.py new file mode 100644 index 0000000..0cf1d15 --- /dev/null +++ b/backend/app/api/routes/settings.py @@ -0,0 +1,78 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app.api.deps import get_current_user, get_db +from app.core.security import verify_password +from app.models.settings import GlobalSettings +from app.models.user import User +from app.schemas.settings import ( + RevealSshKeyRequest, + RevealSshKeyResponse, + SchedulerStatusResponse, + SettingsResponse, + SettingsUpdate, +) +from app.services.notification_service import notification_service +from app.services.scheduler import scheduler_service +from app.services.settings_service import settings_service + +router = APIRouter() + + +def serialize_settings(settings: GlobalSettings) -> SettingsResponse: + payload = SettingsResponse.model_validate(settings, from_attributes=True).model_dump() + payload['global_ssh_key'] = None + payload['has_global_ssh_key'] = bool((settings.global_ssh_key or '').strip()) + payload['has_default_switchos_credentials'] = bool( + (settings.default_switchos_username or '').strip() or (settings.default_switchos_password or '').strip() + ) + return SettingsResponse.model_validate(payload) + + +@router.get('', response_model=SettingsResponse) +def get_settings(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + _ = current_user + settings = settings_service.get_or_create(db) + return serialize_settings(settings) + + +@router.get('/scheduler-status', response_model=SchedulerStatusResponse) +def get_scheduler_status(current_user: User = Depends(get_current_user)): + _ = current_user + return scheduler_service.scheduler_status() + + +@router.put('', response_model=SettingsResponse) +def update_settings(payload: SettingsUpdate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + _ = current_user + settings = settings_service.update(db, payload) + scheduler_service.reschedule() + return serialize_settings(settings) + + +@router.post('/reveal-ssh-key', response_model=RevealSshKeyResponse) +def reveal_ssh_key( + payload: RevealSshKeyRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db), +): + if not verify_password(payload.password, current_user.password_hash): + raise HTTPException(status_code=400, detail='Current password is invalid') + settings = settings_service.get_or_create(db) + return {'global_ssh_key': settings.global_ssh_key} + + +@router.post('/test-email') +def test_email(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + _ = current_user + settings = settings_service.get_or_create(db) + notification_service.send_test_email(settings) + return {'message': 'Test email sent'} + + +@router.post('/test-pushover') +def test_pushover(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + _ = current_user + settings = settings_service.get_or_create(db) + notification_service.send_test_pushover(settings) + return {'message': 'Test pushover sent'} diff --git a/backend/app/api/routes/swos_beta.py b/backend/app/api/routes/swos_beta.py new file mode 100644 index 0000000..71f3565 --- /dev/null +++ b/backend/app/api/routes/swos_beta.py @@ -0,0 +1,33 @@ +from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import StreamingResponse + +from app.api.deps import get_current_user +from app.models.user import User +from app.schemas.swos_beta import SwosBetaCredentials, SwosBetaProbeResponse +from app.services.swos_beta_service import swos_beta_service + +router = APIRouter() + + +@router.post('/probe', response_model=SwosBetaProbeResponse) +def probe_swos(payload: SwosBetaCredentials, current_user: User = Depends(get_current_user)): + del current_user + try: + return swos_beta_service.probe(payload) + except ValueError as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + + +@router.post('/download') +def download_swos_backup(payload: SwosBetaCredentials, current_user: User = Depends(get_current_user)): + del current_user + try: + backup = swos_beta_service.download_backup(payload) + except ValueError as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + + return StreamingResponse( + iter([backup.content]), + media_type=backup.content_type, + headers={'Content-Disposition': f'attachment; filename="{backup.filename}"'}, + ) diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 0000000..6547bd1 --- /dev/null +++ b/backend/app/core/config.py @@ -0,0 +1,38 @@ +from pathlib import Path + +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + app_name: str = 'Mikrotik Backup System' + app_env: str = 'development' + secret_key: str = 'change-me' + jwt_algorithm: str = 'HS256' + access_token_expire_minutes: int = 1440 + database_url: str = 'sqlite:///./storage/routeros_backup_next.db' + data_dir: str = './storage' + allow_registration: bool = True + api_prefix: str = '/api' + timezone: str = 'Europe/Warsaw' + default_admin_username: str = 'admin' + default_admin_password: str = 'admin' + smtp_starttls: bool = True + smtp_timeout_seconds: int = 20 + cors_origins: list[str] = Field(default_factory=lambda: ['http://localhost:4200', 'http://127.0.0.1:4200']) + + model_config = SettingsConfigDict( + env_file='.env', + env_file_encoding='utf-8', + extra='ignore', + env_nested_delimiter='__', + ) + + @property + def data_path(self) -> Path: + path = Path(self.data_dir) + path.mkdir(parents=True, exist_ok=True) + return path + + +settings = Settings() diff --git a/backend/app/core/cron_utils.py b/backend/app/core/cron_utils.py new file mode 100644 index 0000000..c12872e --- /dev/null +++ b/backend/app/core/cron_utils.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +from datetime import datetime +from zoneinfo import ZoneInfo + +from apscheduler.triggers.cron import CronTrigger + +WEEKDAY_LABELS = { + '0': 'Sunday', + '1': 'Monday', + '2': 'Tuesday', + '3': 'Wednesday', + '4': 'Thursday', + '5': 'Friday', + '6': 'Saturday', + '7': 'Sunday', + 'sun': 'Sunday', + 'mon': 'Monday', + 'tue': 'Tuesday', + 'wed': 'Wednesday', + 'thu': 'Thursday', + 'fri': 'Friday', + 'sat': 'Saturday', +} + + +class CronValidationError(ValueError): + pass + + +def parse_cron_expression(expr: str, timezone_str: str) -> CronTrigger: + expr = (expr or '').strip() + if not expr: + raise CronValidationError('Cron expression cannot be empty') + + parts = expr.split() + if len(parts) != 5: + raise CronValidationError('Cron expression must contain exactly 5 fields') + + minute, hour, day, month, day_of_week = parts + try: + return CronTrigger( + minute=minute, + hour=hour, + day=day, + month=month, + day_of_week=day_of_week, + timezone=ZoneInfo(timezone_str), + ) + except Exception as exc: # pragma: no cover - APScheduler formats messages + raise CronValidationError(str(exc)) from exc + + +def validate_cron_expression(expr: str, timezone_str: str) -> None: + parse_cron_expression(expr, timezone_str) + + +def preview_next_runs(expr: str, timezone_str: str, count: int = 3) -> list[datetime]: + trigger = parse_cron_expression(expr, timezone_str) + now = datetime.now(ZoneInfo(timezone_str)) + previous = None + runs: list[datetime] = [] + for _ in range(max(count, 0)): + next_run = trigger.get_next_fire_time(previous, now) + if not next_run: + break + runs.append(next_run) + previous = next_run + now = next_run + return runs + + +def describe_cron_expression(expr: str) -> str: + expr = (expr or '').strip() + if not expr: + return 'Disabled' + + parts = expr.split() + if len(parts) != 5: + return 'Custom cron' + + minute, hour, day, month, day_of_week = parts + if minute.isdigit() and hour.isdigit() and day == '*' and month == '*' and day_of_week == '*': + return f'Every day at {int(hour):02d}:{int(minute):02d}' + if minute.isdigit() and hour.isdigit() and day == '*' and month == '*' and day_of_week.lower() in WEEKDAY_LABELS: + weekday = WEEKDAY_LABELS[day_of_week.lower()] + return f'Every {weekday} at {int(hour):02d}:{int(minute):02d}' + return 'Custom cron' diff --git a/backend/app/core/security.py b/backend/app/core/security.py new file mode 100644 index 0000000..2846a6e --- /dev/null +++ b/backend/app/core/security.py @@ -0,0 +1,22 @@ +from datetime import datetime, timedelta, timezone + +from jose import jwt +from passlib.context import CryptContext + +from app.core.config import settings + +pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto") + + +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) + + +def create_access_token(subject: str, expires_delta: timedelta) -> str: + expire = datetime.now(timezone.utc) + expires_delta + to_encode = {"sub": subject, "exp": expire} + return jwt.encode(to_encode, settings.secret_key, algorithm=settings.jwt_algorithm) diff --git a/backend/app/db/base.py b/backend/app/db/base.py new file mode 100644 index 0000000..61c0929 --- /dev/null +++ b/backend/app/db/base.py @@ -0,0 +1,7 @@ +from app.models.backup import Backup +from app.models.log import OperationLog +from app.models.router import Router +from app.models.settings import GlobalSettings +from app.models.user import User + +__all__ = ["User", "Router", "Backup", "OperationLog", "GlobalSettings"] diff --git a/backend/app/db/session.py b/backend/app/db/session.py new file mode 100644 index 0000000..843b738 --- /dev/null +++ b/backend/app/db/session.py @@ -0,0 +1,84 @@ +from pathlib import Path + +from sqlalchemy import create_engine, inspect, text +from sqlalchemy.orm import declarative_base, sessionmaker + +from app.core.config import settings +from app.core.security import get_password_hash + + +def _ensure_sqlite_parent(database_url: str) -> None: + if not database_url.startswith('sqlite:///'): + return + relative_path = database_url.removeprefix('sqlite:///') + if not relative_path or relative_path == ':memory:': + return + db_path = Path(relative_path) + if not db_path.is_absolute(): + db_path = Path.cwd() / db_path + db_path.parent.mkdir(parents=True, exist_ok=True) + + +_ensure_sqlite_parent(settings.database_url) + +engine = create_engine( + settings.database_url, + connect_args={'check_same_thread': False} if settings.database_url.startswith('sqlite') else {}, +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + + +def _ensure_column(table_name: str, column_name: str, ddl: str) -> None: + inspector = inspect(engine) + existing = {column['name'] for column in inspector.get_columns(table_name)} + if column_name in existing: + return + with engine.begin() as connection: + connection.execute(text(f'ALTER TABLE {table_name} ADD COLUMN {column_name} {ddl}')) + + +def _run_lightweight_migrations() -> None: + tables = set(inspect(engine).get_table_names()) + if 'global_settings' in tables: + _ensure_column('global_settings', 'connection_test_interval_minutes', 'INTEGER DEFAULT 0') + _ensure_column('global_settings', 'default_switchos_username', 'VARCHAR(120)') + _ensure_column('global_settings', 'default_switchos_password', 'VARCHAR(255)') + if 'users' in tables: + _ensure_column('users', 'preferred_language', "VARCHAR(8) DEFAULT 'pl' NOT NULL") + _ensure_column('users', 'preferred_font', "VARCHAR(32) DEFAULT 'default' NOT NULL") + if 'routers' in tables: + _ensure_column('routers', 'device_type', "VARCHAR(32) DEFAULT 'routeros' NOT NULL") + _ensure_column('routers', 'last_connection_status', 'BOOLEAN') + _ensure_column('routers', 'last_connection_tested_at', 'DATETIME') + _ensure_column('routers', 'last_connection_error', 'TEXT') + _ensure_column('routers', 'last_connection_hostname', 'VARCHAR(255)') + _ensure_column('routers', 'last_connection_model', 'VARCHAR(255)') + _ensure_column('routers', 'last_connection_version', 'VARCHAR(255)') + _ensure_column('routers', 'last_connection_uptime', 'VARCHAR(255)') + _ensure_column('routers', 'last_connection_transport', 'VARCHAR(32)') + _ensure_column('routers', 'last_connection_server', 'VARCHAR(255)') + _ensure_column('routers', 'last_connection_auth_mode', 'VARCHAR(64)') + _ensure_column('routers', 'last_connection_http_status', 'VARCHAR(32)') + _ensure_column('routers', 'last_connection_backup_available', 'BOOLEAN') + + +def init_db(): + import app.db.base # noqa: F401 + from app.models.settings import GlobalSettings + from app.models.user import User + + Base.metadata.create_all(bind=engine) + _run_lightweight_migrations() + with SessionLocal() as db: + if not db.query(User).first(): + db.add( + User( + username=settings.default_admin_username, + password_hash=get_password_hash(settings.default_admin_password), + ) + ) + db.commit() + if not db.query(GlobalSettings).first(): + db.add(GlobalSettings()) + db.commit() diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..c060f9f --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,30 @@ +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.api import api_router +from app.core.config import settings +from app.db.session import init_db +from app.services.scheduler import scheduler_service + + +@asynccontextmanager +async def lifespan(app: FastAPI): + init_db() + scheduler_service.start() + try: + yield + finally: + scheduler_service.shutdown() + + +app = FastAPI(title=settings.app_name, version="1.1.0", lifespan=lifespan) +app.add_middleware( + CORSMiddleware, + allow_origins=settings.cors_origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +app.include_router(api_router, prefix=settings.api_prefix) diff --git a/backend/app/models/backup.py b/backend/app/models/backup.py new file mode 100644 index 0000000..7c1ccae --- /dev/null +++ b/backend/app/models/backup.py @@ -0,0 +1,19 @@ +from sqlalchemy import Column, DateTime, ForeignKey, Integer, String +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func + +from app.db.session import Base + + +class Backup(Base): + __tablename__ = "backups" + + id = Column(Integer, primary_key=True, index=True) + router_id = Column(Integer, ForeignKey("routers.id"), nullable=False, index=True) + file_path = Column(String(500), nullable=False) + file_name = Column(String(255), nullable=False) + backup_type = Column(String(50), nullable=False, default="export") + checksum = Column(String(64), nullable=True) + created_at = Column(DateTime, server_default=func.now(), nullable=False) + + router = relationship("Router", back_populates="backups") diff --git a/backend/app/models/log.py b/backend/app/models/log.py new file mode 100644 index 0000000..cd5ad69 --- /dev/null +++ b/backend/app/models/log.py @@ -0,0 +1,12 @@ +from sqlalchemy import Column, DateTime, Integer, Text +from sqlalchemy.sql import func + +from app.db.session import Base + + +class OperationLog(Base): + __tablename__ = "operation_logs" + + id = Column(Integer, primary_key=True, index=True) + message = Column(Text, nullable=False) + timestamp = Column(DateTime, server_default=func.now(), nullable=False) diff --git a/backend/app/models/router.py b/backend/app/models/router.py new file mode 100644 index 0000000..d998e5b --- /dev/null +++ b/backend/app/models/router.py @@ -0,0 +1,34 @@ +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func + +from app.db.session import Base + + +class Router(Base): + __tablename__ = "routers" + + id = Column(Integer, primary_key=True, index=True) + owner_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True) + name = Column(String(120), nullable=False) + device_type = Column(String(32), nullable=False, default="routeros") + host = Column(String(255), nullable=False) + port = Column(Integer, nullable=False, default=22) + ssh_user = Column(String(120), nullable=False, default="admin") + ssh_key = Column(Text, nullable=True) + ssh_password = Column(String(255), nullable=True) + last_connection_status = Column(Boolean, nullable=True) + last_connection_tested_at = Column(DateTime, nullable=True) + last_connection_error = Column(Text, nullable=True) + last_connection_hostname = Column(String(255), nullable=True) + last_connection_model = Column(String(255), nullable=True) + last_connection_version = Column(String(255), nullable=True) + last_connection_uptime = Column(String(255), nullable=True) + last_connection_transport = Column(String(32), nullable=True) + last_connection_server = Column(String(255), nullable=True) + last_connection_auth_mode = Column(String(64), nullable=True) + last_connection_http_status = Column(String(32), nullable=True) + last_connection_backup_available = Column(Boolean, nullable=True) + created_at = Column(DateTime, server_default=func.now(), nullable=False) + + backups = relationship("Backup", back_populates="router", cascade="all, delete-orphan") diff --git a/backend/app/models/settings.py b/backend/app/models/settings.py new file mode 100644 index 0000000..29e71fe --- /dev/null +++ b/backend/app/models/settings.py @@ -0,0 +1,28 @@ +from sqlalchemy import Boolean, Column, Integer, String, Text + +from app.db.session import Base + + +class GlobalSettings(Base): + __tablename__ = "global_settings" + + id = Column(Integer, primary_key=True) + backup_retention_days = Column(Integer, default=7) + log_retention_days = Column(Integer, default=7) + export_cron = Column(String(64), default="") + binary_cron = Column(String(64), default="") + retention_cron = Column(String(64), default="") + enable_auto_export = Column(Boolean, default=False) + connection_test_interval_minutes = Column(Integer, default=0) + global_ssh_key = Column(Text, nullable=True) + default_switchos_username = Column(String(120), nullable=True) + default_switchos_password = Column(String(255), nullable=True) + pushover_token = Column(String(255), nullable=True) + pushover_userkey = Column(String(255), nullable=True) + notify_failures_only = Column(Boolean, default=True) + smtp_host = Column(String(255), nullable=True) + smtp_port = Column(Integer, default=587) + smtp_login = Column(String(255), nullable=True) + smtp_password = Column(String(255), nullable=True) + smtp_notifications_enabled = Column(Boolean, default=False) + recipient_email = Column(String(255), nullable=True) diff --git a/backend/app/models/user.py b/backend/app/models/user.py new file mode 100644 index 0000000..b8206bd --- /dev/null +++ b/backend/app/models/user.py @@ -0,0 +1,15 @@ +from sqlalchemy import Column, DateTime, Integer, String +from sqlalchemy.sql import func + +from app.db.session import Base + + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + username = Column(String(120), unique=True, nullable=False, index=True) + password_hash = Column(String(255), nullable=False) + created_at = Column(DateTime, server_default=func.now(), nullable=False) + preferred_language = Column(String(8), nullable=False, default='pl') + preferred_font = Column(String(32), nullable=False, default='default') diff --git a/backend/app/schemas/auth.py b/backend/app/schemas/auth.py new file mode 100644 index 0000000..0df53f5 --- /dev/null +++ b/backend/app/schemas/auth.py @@ -0,0 +1,31 @@ +from pydantic import BaseModel, Field + + +class UserResponse(BaseModel): + id: int + username: str + preferred_language: str = 'pl' + preferred_font: str = 'default' + + model_config = {"from_attributes": True} + + +class TokenResponse(BaseModel): + access_token: str + token_type: str = "bearer" + user: UserResponse + + +class RegisterRequest(BaseModel): + username: str = Field(min_length=3, max_length=120) + password: str = Field(min_length=4, max_length=128) + + +class ChangePasswordRequest(BaseModel): + current_password: str + new_password: str = Field(min_length=4, max_length=128) + + +class UpdateUserPreferencesRequest(BaseModel): + preferred_language: str = Field(default='pl', min_length=2, max_length=8) + preferred_font: str = Field(default='default', min_length=2, max_length=32) diff --git a/backend/app/schemas/backup.py b/backend/app/schemas/backup.py new file mode 100644 index 0000000..85368ca --- /dev/null +++ b/backend/app/schemas/backup.py @@ -0,0 +1,50 @@ +from datetime import datetime +from typing import Literal + +from pydantic import BaseModel + + +class BackupResponse(BaseModel): + id: int + router_id: int + router_name: str | None = None + device_type: str = "routeros" + file_path: str + file_name: str + backup_type: str + checksum: str | None = None + file_size: int | None = None + created_at: datetime + + model_config = {'from_attributes': True} + + +class BackupDiffLine(BaseModel): + type: Literal['context', 'added', 'removed', 'modified'] + left_number: int | None = None + right_number: int | None = None + left_text: str = '' + right_text: str = '' + + +class BackupDiffStats(BaseModel): + added: int = 0 + removed: int = 0 + modified: int = 0 + context: int = 0 + + +class BackupDiffResponse(BaseModel): + left_backup_id: int + right_backup_id: int + left_file_name: str | None = None + right_file_name: str | None = None + diff_text: str + diff_html: str | None = None + stats: BackupDiffStats | None = None + lines: list[BackupDiffLine] = [] + + +class BulkActionRequest(BaseModel): + action: Literal['download', 'delete'] + backup_ids: list[int] diff --git a/backend/app/schemas/dashboard.py b/backend/app/schemas/dashboard.py new file mode 100644 index 0000000..7ad7bbb --- /dev/null +++ b/backend/app/schemas/dashboard.py @@ -0,0 +1,28 @@ +from datetime import datetime + +from pydantic import BaseModel + + +class StorageStats(BaseModel): + total: int + used: int + free: int + folder_used: int + usage_percent: float + + +class OperationLogResponse(BaseModel): + id: int + message: str + timestamp: datetime + + model_config = {"from_attributes": True} + + +class DashboardResponse(BaseModel): + routers_count: int + export_count: int + binary_count: int + total_backups: int + storage: StorageStats + recent_logs: list[OperationLogResponse] diff --git a/backend/app/schemas/router.py b/backend/app/schemas/router.py new file mode 100644 index 0000000..66c1809 --- /dev/null +++ b/backend/app/schemas/router.py @@ -0,0 +1,104 @@ +import re +from datetime import datetime +from typing import Literal + +from pydantic import BaseModel, Field, field_validator, model_validator + +ALLOWED_NAME_REGEX = re.compile(r"^[A-Za-z0-9_-]+$") +DeviceType = Literal["routeros", "switchos"] + + +class RouterBase(BaseModel): + name: str = Field(min_length=1, max_length=120) + device_type: DeviceType = "routeros" + host: str = Field(min_length=1, max_length=255) + port: int | None = Field(default=None, ge=1, le=65535) + ssh_user: str | None = Field(default=None, max_length=120) + ssh_key: str | None = None + ssh_password: str | None = None + + @field_validator("name") + @classmethod + def validate_name(cls, value: str) -> str: + if not ALLOWED_NAME_REGEX.match(value): + raise ValueError("Only letters, digits, dashes and underscores are allowed") + return value + + @field_validator("host", "ssh_user", "ssh_key", "ssh_password", mode="before") + @classmethod + def normalize_text(cls, value: str | None) -> str | None: + normalized = (value or "").strip() + return normalized or None + + @model_validator(mode="after") + def apply_device_defaults(self): + if self.device_type == "routeros": + self.port = self.port or 22 + self.ssh_user = self.ssh_user or "admin" + return self + + self.port = self.port or 80 + self.ssh_key = None + return self + + +class RouterCreate(RouterBase): + pass + + +class RouterUpdate(BaseModel): + name: str | None = None + device_type: DeviceType | None = None + host: str | None = None + port: int | None = Field(default=None, ge=1, le=65535) + ssh_user: str | None = None + ssh_key: str | None = None + ssh_password: str | None = None + + @field_validator("name", "host", "ssh_user", "ssh_key", "ssh_password", mode="before") + @classmethod + def normalize_text(cls, value: str | None) -> str | None: + normalized = (value or "").strip() + return normalized or None + + +class RouterResponse(RouterBase): + id: int + owner_id: int + effective_username: str | None = None + uses_global_ssh_key: bool = False + has_effective_ssh_key: bool = False + uses_global_switchos_credentials: bool = False + has_effective_password: bool = False + supports_export: bool = False + supports_restore_upload: bool = False + last_connection_status: bool | None = None + last_connection_tested_at: datetime | None = None + last_connection_error: str | None = None + last_connection_hostname: str | None = None + last_connection_model: str | None = None + last_connection_version: str | None = None + last_connection_uptime: str | None = None + last_connection_transport: str | None = None + last_connection_server: str | None = None + last_connection_auth_mode: str | None = None + last_connection_http_status: str | None = None + last_connection_backup_available: bool | None = None + created_at: datetime | None = None + + model_config = {"from_attributes": True} + + +class RouterTestConnection(BaseModel): + success: bool + tested_at: datetime + model: str + uptime: str + hostname: str + version: str | None = None + error: str | None = None + transport: str | None = None + server: str | None = None + auth_mode: str | None = None + http_status: str | None = None + backup_available: bool | None = None diff --git a/backend/app/schemas/settings.py b/backend/app/schemas/settings.py new file mode 100644 index 0000000..a2a9908 --- /dev/null +++ b/backend/app/schemas/settings.py @@ -0,0 +1,88 @@ +from datetime import datetime + +from pydantic import BaseModel, EmailStr, Field, field_validator + +from app.core.config import settings as app_settings +from app.core.cron_utils import CronValidationError, validate_cron_expression + + +class SettingsBase(BaseModel): + backup_retention_days: int = 7 + log_retention_days: int = 7 + export_cron: str = '' + binary_cron: str = '' + retention_cron: str = '' + enable_auto_export: bool = False + connection_test_interval_minutes: int = Field(default=0, ge=0, le=1440) + global_ssh_key: str | None = None + default_switchos_username: str | None = None + default_switchos_password: str | None = None + pushover_token: str | None = None + pushover_userkey: str | None = None + notify_failures_only: bool = True + smtp_host: str | None = None + smtp_port: int = 587 + smtp_login: str | None = None + smtp_password: str | None = None + smtp_notifications_enabled: bool = False + recipient_email: EmailStr | None = None + + @field_validator('export_cron', 'binary_cron', 'retention_cron', mode='before') + @classmethod + def normalize_cron(cls, value: str | None) -> str: + return (value or '').strip() + + @field_validator('global_ssh_key', 'default_switchos_username', 'default_switchos_password', mode='before') + @classmethod + def normalize_secret_text(cls, value: str | None) -> str | None: + normalized = (value or '').strip() + return normalized or None + + @field_validator('export_cron', 'binary_cron', 'retention_cron') + @classmethod + def validate_cron(cls, value: str) -> str: + if not value: + return value + try: + validate_cron_expression(value, app_settings.timezone) + except CronValidationError as exc: + raise ValueError(f'Invalid cron expression: {exc}') from exc + return value + + +class SettingsUpdate(SettingsBase): + clear_global_ssh_key: bool = False + + +class SettingsResponse(SettingsBase): + id: int + has_global_ssh_key: bool = False + has_default_switchos_credentials: bool = False + + model_config = {'from_attributes': True} + + +class RevealSshKeyRequest(BaseModel): + password: str + + +class RevealSshKeyResponse(BaseModel): + global_ssh_key: str | None = None + + +class SchedulerJobStatus(BaseModel): + key: str + label: str + enabled: bool + cron: str | None = None + description: str + description_params: dict[str, str | int] | None = None + valid: bool + next_runs: list[datetime] = [] + error: str | None = None + + +class SchedulerStatusResponse(BaseModel): + timezone: str + running: bool + jobs: list[SchedulerJobStatus] diff --git a/backend/app/schemas/swos_beta.py b/backend/app/schemas/swos_beta.py new file mode 100644 index 0000000..be1079a --- /dev/null +++ b/backend/app/schemas/swos_beta.py @@ -0,0 +1,33 @@ +from pydantic import BaseModel, Field, field_validator + + +class SwosBetaCredentials(BaseModel): + host: str = Field(min_length=1, max_length=255) + port: int = Field(default=80, ge=1, le=65535) + username: str = Field(default='admin', min_length=1, max_length=120) + password: str = Field(default='', max_length=255) + label: str | None = Field(default=None, max_length=120) + + @field_validator('host', 'username', 'password', mode='before') + @classmethod + def normalize_text(cls, value: str | None) -> str: + return (value or '').strip() + + @field_validator('label', mode='before') + @classmethod + def normalize_label(cls, value: str | None) -> str | None: + normalized = (value or '').strip() + return normalized or None + + +class SwosBetaProbeResponse(BaseModel): + success: bool + base_url: str + status_code: int + auth_mode: str + page_title: str | None = None + content_type: str | None = None + server: str | None = None + save_backup_visible: bool = False + backup_endpoint_ok: bool = False + note: str | None = None diff --git a/backend/app/services/backup_service.py b/backend/app/services/backup_service.py new file mode 100644 index 0000000..56fe4fe --- /dev/null +++ b/backend/app/services/backup_service.py @@ -0,0 +1,350 @@ +import difflib +from datetime import date, datetime, time, timedelta, timezone +from pathlib import Path + +from fastapi import HTTPException +from sqlalchemy.orm import Session, joinedload + +from app.models.backup import Backup +from app.models.router import Router +from app.models.user import User +from app.services.file_service import compute_checksum, ensure_data_dir +from app.services.log_service import log_service +from app.services.notification_service import notification_service +from app.services.router_service import router_service +from app.services.settings_service import settings_service + + +class BackupService: + def _device_label(self, router: Router) -> str: + platform = 'SwitchOS' if router.device_type == 'switchos' else 'RouterOS' + return f'{platform} device {router.name}' + + def _router_for_user(self, db: Session, user: User, router_id: int) -> Router: + router = db.query(Router).filter(Router.id == router_id, Router.owner_id == user.id).first() + if not router: + raise HTTPException(status_code=404, detail='Device not found') + return router + + def _serialize_backup(self, backup: Backup): + file_path = Path(backup.file_path) + return { + 'id': backup.id, + 'router_id': backup.router_id, + 'router_name': backup.router.name if backup.router else None, + 'device_type': backup.router.device_type if backup.router else 'routeros', + 'file_path': backup.file_path, + 'file_name': backup.file_name, + 'backup_type': backup.backup_type, + 'checksum': backup.checksum, + 'file_size': file_path.stat().st_size if file_path.exists() else None, + 'created_at': backup.created_at, + } + + def _build_structured_diff(self, left_lines: list[str], right_lines: list[str]): + matcher = difflib.SequenceMatcher(a=left_lines, b=right_lines) + rows = [] + stats = {'added': 0, 'removed': 0, 'modified': 0, 'context': 0} + left_number = 1 + right_number = 1 + + for tag, i1, i2, j1, j2 in matcher.get_opcodes(): + if tag == 'equal': + for left_text, right_text in zip(left_lines[i1:i2], right_lines[j1:j2]): + rows.append( + { + 'type': 'context', + 'left_number': left_number, + 'right_number': right_number, + 'left_text': left_text, + 'right_text': right_text, + } + ) + stats['context'] += 1 + left_number += 1 + right_number += 1 + continue + + if tag == 'delete': + for left_text in left_lines[i1:i2]: + rows.append( + { + 'type': 'removed', + 'left_number': left_number, + 'right_number': None, + 'left_text': left_text, + 'right_text': '', + } + ) + stats['removed'] += 1 + left_number += 1 + continue + + if tag == 'insert': + for right_text in right_lines[j1:j2]: + rows.append( + { + 'type': 'added', + 'left_number': None, + 'right_number': right_number, + 'left_text': '', + 'right_text': right_text, + } + ) + stats['added'] += 1 + right_number += 1 + continue + + block_left = left_lines[i1:i2] + block_right = right_lines[j1:j2] + block_size = max(len(block_left), len(block_right)) + for index in range(block_size): + left_text = block_left[index] if index < len(block_left) else '' + right_text = block_right[index] if index < len(block_right) else '' + row_type = 'modified' + if left_text and not right_text: + row_type = 'removed' + stats['removed'] += 1 + elif right_text and not left_text: + row_type = 'added' + stats['added'] += 1 + else: + stats['modified'] += 1 + + rows.append( + { + 'type': row_type, + 'left_number': left_number if left_text else None, + 'right_number': right_number if right_text else None, + 'left_text': left_text, + 'right_text': right_text, + } + ) + if left_text: + left_number += 1 + if right_text: + right_number += 1 + + return rows, stats + + def get_backup_for_user(self, db: Session, user: User, backup_id: int) -> Backup: + backup = ( + db.query(Backup) + .options(joinedload(Backup.router)) + .join(Router) + .filter(Backup.id == backup_id, Router.owner_id == user.id) + .first() + ) + if not backup: + raise HTTPException(status_code=404, detail='Backup not found') + return backup + + def list_backups( + self, + db: Session, + user: User, + search: str | None = None, + backup_type: str | None = None, + router_id: int | None = None, + created_on: date | None = None, + sort_by: str = 'created_at', + order: str = 'desc', + ): + query = db.query(Backup).options(joinedload(Backup.router)).join(Router).filter(Router.owner_id == user.id) + if search: + query = query.filter( + Backup.file_name.ilike(f'%{search}%') + | Router.name.ilike(f'%{search}%') + | Router.host.ilike(f'%{search}%') + ) + if backup_type: + query = query.filter(Backup.backup_type == backup_type) + if router_id: + query = query.filter(Backup.router_id == router_id) + if created_on: + day_start = datetime.combine(created_on, time.min) + next_day = day_start + timedelta(days=1) + query = query.filter(Backup.created_at >= day_start, Backup.created_at < next_day) + + sort_map = { + 'created_at': Backup.created_at, + 'file_name': Backup.file_name, + 'backup_type': Backup.backup_type, + 'router_name': Router.name, + } + sort_column = sort_map.get(sort_by, Backup.created_at) + query = query.order_by(sort_column.asc() if order == 'asc' else sort_column.desc()) + return [self._serialize_backup(backup) for backup in query.all()] + + def list_router_backups(self, db: Session, user: User, router_id: int): + router = self._router_for_user(db, user, router_id) + backups = ( + db.query(Backup) + .options(joinedload(Backup.router)) + .filter(Backup.router_id == router.id) + .order_by(Backup.created_at.desc()) + .all() + ) + return [self._serialize_backup(backup) for backup in backups] + + def export_router(self, db: Session, user: User, router_id: int) -> Backup: + router = self._router_for_user(db, user, router_id) + if router.device_type != 'routeros': + raise HTTPException(status_code=400, detail='Text export is available only for RouterOS devices') + settings = settings_service.get_or_create(db) + stamp = datetime.now().strftime('%Y%m%d_%H%M%S') + name = f'{router.name}_{router.id}_{stamp}.rsc' + file_path = ensure_data_dir() / name + try: + content = router_service.export(router, settings.global_ssh_key) + file_path.write_text(content, encoding='utf-8') + backup = Backup(router_id=router.id, file_path=str(file_path), file_name=name, backup_type='export') + db.add(backup) + db.commit() + db.refresh(backup) + log_service.add(db, f'Export OK for device {router.name}') + notification_service.notify(settings, f'Export {router.name} OK', True) + return backup + except HTTPException: + raise + except Exception as exc: + notification_service.notify(settings, f'Export {router.name} FAIL: {exc}', False) + log_service.add(db, f'Export FAILED for device {router.name}: {exc}') + raise HTTPException(status_code=500, detail=str(exc)) from exc + + def binary_backup(self, db: Session, user: User, router_id: int) -> Backup: + router = self._router_for_user(db, user, router_id) + settings = settings_service.get_or_create(db) + stamp = datetime.now().strftime('%Y%m%d_%H%M%S') + base_name = f'{router.name}_{router.id}_{stamp}' + extension = '.swb' if router.device_type == 'switchos' else '.backup' + name = f'{base_name}{extension}' + file_path = ensure_data_dir() / name + try: + router_service.binary_backup(router, base_name, str(file_path), settings.global_ssh_key, settings) + checksum = compute_checksum(str(file_path)) + backup = Backup(router_id=router.id, file_path=str(file_path), file_name=name, backup_type='binary', checksum=checksum) + db.add(backup) + db.commit() + db.refresh(backup) + log_service.add(db, f'Binary backup OK for {self._device_label(router)}') + notification_service.notify(settings, f'Backup {router.name} OK', True) + return backup + except HTTPException: + raise + except Exception as exc: + notification_service.notify(settings, f'Backup {router.name} FAIL: {exc}', False) + log_service.add(db, f'Binary backup FAILED for {self._device_label(router)}: {exc}') + raise HTTPException(status_code=500, detail=str(exc)) from exc + + def upload_backup_to_router(self, db: Session, user: User, router_id: int, backup_id: int): + router = self._router_for_user(db, user, router_id) + if router.device_type != 'routeros': + raise HTTPException(status_code=400, detail='Restore upload is available only for RouterOS devices') + backup = self.get_backup_for_user(db, user, backup_id) + if backup.backup_type != 'binary': + raise HTTPException(status_code=400, detail='Only binary backups can be uploaded') + if backup.router and backup.router.device_type != 'routeros': + raise HTTPException(status_code=400, detail='SwitchOS backup files cannot be restored over SSH upload') + checksum = compute_checksum(backup.file_path) + if backup.checksum and checksum != backup.checksum: + raise HTTPException(status_code=400, detail='Checksum mismatch') + settings = settings_service.get_or_create(db) + router_service.upload_backup(router, backup.file_path, settings.global_ssh_key) + log_service.add(db, f'Upload backup OK for device {router.name}') + + def delete_backup(self, db: Session, user: User, backup_id: int, commit: bool = True): + backup = self.get_backup_for_user(db, user, backup_id) + path = Path(backup.file_path) + if path.exists(): + path.unlink() + db.delete(backup) + if commit: + db.commit() + + def diff_backups(self, db: Session, user: User, left_id: int, right_id: int): + left = self.get_backup_for_user(db, user, left_id) + right = self.get_backup_for_user(db, user, right_id) + if left.backup_type != 'export' or right.backup_type != 'export': + raise HTTPException(status_code=400, detail='Diff is supported only for export backups') + left_lines = Path(left.file_path).read_text(encoding='utf-8', errors='ignore').splitlines() + right_lines = Path(right.file_path).read_text(encoding='utf-8', errors='ignore').splitlines() + diff_lines = list( + difflib.unified_diff(left_lines, right_lines, fromfile=left.file_name, tofile=right.file_name, lineterm='') + ) + diff_html = difflib.HtmlDiff(wrapcolumn=120).make_file( + left_lines, + right_lines, + fromdesc=left.file_name, + todesc=right.file_name, + context=True, + numlines=2, + ) + structured_lines, stats = self._build_structured_diff(left_lines, right_lines) + return { + 'left_backup_id': left_id, + 'right_backup_id': right_id, + 'left_file_name': left.file_name, + 'right_file_name': right.file_name, + 'diff_text': '\n'.join(diff_lines), + 'diff_html': diff_html, + 'stats': stats, + 'lines': structured_lines, + } + + def email_backup(self, db: Session, user: User, backup_id: int): + backup = self.get_backup_for_user(db, user, backup_id) + settings = settings_service.get_or_create(db) + platform_name = 'SwitchOS' if backup.router and backup.router.device_type == 'switchos' else 'RouterOS' + noun = 'Export' if backup.backup_type == 'export' else 'Backup' + subject = f'{platform_name} {noun}: {backup.file_name}' + body = f'Sending {backup.file_name} from device {backup.router.name}.' + notification_service.send_email(settings, subject, body, backup.file_path) + log_service.add(db, f'Email sent for backup {backup.file_name}') + + def export_all(self, db: Session, user: User): + routers = db.query(Router).filter(Router.owner_id == user.id).all() + result = [] + for router in routers: + if (router.device_type or 'routeros').lower() != 'routeros': + result.append({ + 'router': router.name, + 'status': 'skipped', + 'message': 'Text export is available only for RouterOS devices', + }) + continue + try: + backup = self.export_router(db, user, router.id) + result.append({'router': router.name, 'status': 'ok', 'backup_id': backup.id}) + except Exception as exc: + result.append({'router': router.name, 'status': 'error', 'message': str(exc)}) + return result + + def binary_all(self, db: Session, user: User): + routers = db.query(Router).filter(Router.owner_id == user.id).all() + result = [] + for router in routers: + try: + backup = self.binary_backup(db, user, router.id) + result.append({'router': router.name, 'status': 'ok', 'backup_id': backup.id}) + except Exception as exc: + result.append({'router': router.name, 'status': 'error', 'message': str(exc)}) + return result + + def cleanup_old_backups(self, db: Session): + settings = settings_service.get_or_create(db) + cutoff = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(days=settings.backup_retention_days) + old_backups = db.query(Backup).filter(Backup.created_at < cutoff).all() + deleted_count = 0 + for backup in old_backups: + path = Path(backup.file_path) + if path.exists(): + path.unlink() + db.delete(backup) + deleted_count += 1 + db.commit() + log_service.add(db, f'Retention cleanup removed {deleted_count} backups older than {settings.backup_retention_days} days') + return deleted_count + + +backup_service = BackupService() diff --git a/backend/app/services/file_service.py b/backend/app/services/file_service.py new file mode 100644 index 0000000..d0ff758 --- /dev/null +++ b/backend/app/services/file_service.py @@ -0,0 +1,38 @@ +import hashlib +import os +import shutil +from pathlib import Path + +from app.core.config import settings +from app.schemas.dashboard import StorageStats + + +def compute_checksum(file_path: str) -> str: + sha256 = hashlib.sha256() + with open(file_path, "rb") as handle: + for chunk in iter(lambda: handle.read(4096), b""): + sha256.update(chunk) + return sha256.hexdigest() + + +def ensure_data_dir() -> Path: + return settings.data_path + + +def get_folder_size() -> int: + total = 0 + for dirpath, _, filenames in os.walk(ensure_data_dir()): + for filename in filenames: + try: + total += os.path.getsize(Path(dirpath) / filename) + except OSError: + pass + return total + + +def get_storage_stats() -> StorageStats: + ensure_data_dir() + disk = shutil.disk_usage(ensure_data_dir()) + folder_used = get_folder_size() + usage_percent = (folder_used / disk.total) * 100 if disk.total else 0 + return StorageStats(total=disk.total, used=disk.used, free=disk.free, folder_used=folder_used, usage_percent=usage_percent) diff --git a/backend/app/services/log_service.py b/backend/app/services/log_service.py new file mode 100644 index 0000000..83dcaa3 --- /dev/null +++ b/backend/app/services/log_service.py @@ -0,0 +1,24 @@ +from datetime import datetime, timedelta, timezone + +from sqlalchemy.orm import Session + +from app.models.log import OperationLog + + +class LogService: + def add(self, db: Session, message: str, commit: bool = True) -> None: + db.add(OperationLog(message=message)) + if commit: + db.commit() + + def delete_older_than(self, db: Session, days: int) -> int: + cutoff = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(days=days) + logs = db.query(OperationLog).filter(OperationLog.timestamp < cutoff).all() + count = len(logs) + for log in logs: + db.delete(log) + db.commit() + return count + + +log_service = LogService() diff --git a/backend/app/services/notification_service.py b/backend/app/services/notification_service.py new file mode 100644 index 0000000..f2115db --- /dev/null +++ b/backend/app/services/notification_service.py @@ -0,0 +1,78 @@ +import smtplib +from email import encoders +from email.mime.base import MIMEBase +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from pathlib import Path + +import requests + +from app.core.config import settings as app_settings +from app.models.settings import GlobalSettings + + +class NotificationService: + def send_pushover(self, token: str, user_key: str, message: str, title: str = "RouterOS Backup") -> bool: + response = requests.post( + "https://api.pushover.net/1/messages.json", + data={"token": token, "user": user_key, "message": message, "title": title}, + timeout=15, + ) + return response.ok + + def send_email(self, settings: GlobalSettings, subject: str, body: str, attachment_path: str | None = None): + if not (settings.smtp_host and settings.smtp_login and settings.smtp_password): + raise ValueError("SMTP is not configured") + recipient = (settings.recipient_email or settings.smtp_login or "").strip() + if not recipient: + raise ValueError("Recipient email is empty") + + msg = MIMEMultipart() + msg["From"] = settings.smtp_login + msg["To"] = recipient + msg["Subject"] = subject + msg.attach(MIMEText(body, "plain", "utf-8")) + + if attachment_path: + attachment = Path(attachment_path) + with attachment.open("rb") as handle: + part = MIMEBase("application", "octet-stream") + part.set_payload(handle.read()) + encoders.encode_base64(part) + part.add_header("Content-Disposition", f'attachment; filename="{attachment.name}"') + msg.attach(part) + + with smtplib.SMTP(settings.smtp_host, settings.smtp_port, timeout=app_settings.smtp_timeout_seconds) as server: + if app_settings.smtp_starttls: + server.starttls() + server.login(settings.smtp_login, settings.smtp_password) + server.sendmail(settings.smtp_login, [recipient], msg.as_string()) + + def notify(self, settings: GlobalSettings, message: str, success: bool): + if settings.notify_failures_only and success: + return + if settings.smtp_notifications_enabled: + try: + self.send_email(settings, "Mikrotik Backup System notification", message) + except Exception: + pass + if settings.pushover_token and settings.pushover_userkey: + try: + self.send_pushover(settings.pushover_token, settings.pushover_userkey, message) + except Exception: + pass + + def send_test_email(self, settings: GlobalSettings): + self.send_email(settings, "Mikrotik Backup System test", "This is a test email from Mikrotik Backup System") + + def send_test_pushover(self, settings: GlobalSettings): + if not (settings.pushover_token and settings.pushover_userkey): + raise ValueError("Pushover is not configured") + self.send_pushover( + settings.pushover_token, + settings.pushover_userkey, + "Test pushover from Mikrotik Backup System", + ) + + +notification_service = NotificationService() diff --git a/backend/app/services/router_service.py b/backend/app/services/router_service.py new file mode 100644 index 0000000..c682b8d --- /dev/null +++ b/backend/app/services/router_service.py @@ -0,0 +1,220 @@ +from datetime import datetime +import io +from pathlib import Path + +import paramiko +from sqlalchemy.orm import Session + +from app.models.router import Router +from app.services.log_service import log_service +from app.services.swos_beta_service import swos_beta_service + + +class RouterService: + def _load_pkey(self, ssh_key_str: str): + key_str = (ssh_key_str or "").strip() + key_buffer = io.StringIO(key_str) + loaders = [ + paramiko.RSAKey.from_private_key, + paramiko.Ed25519Key.from_private_key, + paramiko.ECDSAKey.from_private_key, + ] + last_error = None + for loader in loaders: + key_buffer.seek(0) + try: + return loader(key_buffer) + except Exception as exc: + last_error = exc + raise ValueError("Failed to load SSH private key") from last_error + + def _connect(self, router: Router, global_ssh_key: str | None = None): + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + key_source = router.ssh_key.strip() if router.ssh_key and router.ssh_key.strip() else (global_ssh_key or "") + if key_source: + pkey = self._load_pkey(key_source) + client.connect(router.host, port=router.port, username=router.ssh_user, pkey=pkey, timeout=10) + else: + client.connect( + router.host, + port=router.port, + username=router.ssh_user, + password=router.ssh_password, + timeout=10, + allow_agent=False, + look_for_keys=False, + banner_timeout=10, + ) + return client + + def export(self, router: Router, global_ssh_key: str | None = None) -> str: + if router.device_type != 'routeros': + raise ValueError('Export tekstowy jest dostępny tylko dla RouterOS.') + client = self._connect(router, global_ssh_key) + _, stdout, _ = client.exec_command('/export') + output = stdout.read().decode('utf-8', errors='ignore') + client.close() + return output + + def binary_backup(self, router: Router, backup_name: str, local_path: str, global_ssh_key: str | None = None, global_settings=None) -> str: + if router.device_type == 'switchos': + downloaded = swos_beta_service.download_backup_for_router(router, global_settings) + Path(local_path).write_bytes(downloaded.content) + return local_path + + client = self._connect(router, global_ssh_key) + _, stdout, _ = client.exec_command(f'/system backup save name={backup_name}') + stdout.channel.recv_exit_status() + sftp = client.open_sftp() + remote_file = f'{backup_name}.backup' + sftp.get(remote_file, local_path) + try: + sftp.remove(remote_file) + except Exception: + pass + sftp.close() + client.close() + return local_path + + def upload_backup(self, router: Router, local_backup_path: str, global_ssh_key: str | None = None): + if router.device_type != 'routeros': + raise ValueError('Przywracanie plików jest dostępne tylko dla RouterOS.') + client = self._connect(router, global_ssh_key) + sftp = client.open_sftp() + target_name = Path(local_backup_path).name + sftp.put(local_backup_path, target_name) + sftp.close() + client.close() + + def _probe_routeros_connection(self, router: Router, global_ssh_key: str | None = None): + tested_at = datetime.utcnow() + try: + client = self._connect(router, global_ssh_key) + _, stdout, _ = client.exec_command('/system resource print without-paging') + resource_output = stdout.read().decode('utf-8', errors='ignore') + _, stdout, _ = client.exec_command('/system identity print') + identity_output = stdout.read().decode('utf-8', errors='ignore') + client.close() + model = 'Unknown' + uptime = 'Unknown' + hostname = 'Unknown' + version = 'Unknown' + for line in resource_output.splitlines(): + if 'board-name' in line: + model = line.split(':', 1)[1].strip() + if 'uptime' in line: + uptime = line.split(':', 1)[1].strip() + if 'version' in line: + version = line.split(':', 1)[1].strip() + for line in identity_output.splitlines(): + if 'name' in line: + hostname = line.split(':', 1)[1].strip() + return { + 'success': True, + 'tested_at': tested_at, + 'model': model, + 'uptime': uptime, + 'hostname': hostname, + 'version': version, + 'error': None, + 'transport': 'ssh', + 'server': None, + 'auth_mode': 'ssh', + 'http_status': None, + 'backup_available': None, + } + except Exception as exc: + return { + 'success': False, + 'tested_at': tested_at, + 'model': 'Unknown', + 'uptime': 'Unknown', + 'hostname': router.name, + 'version': None, + 'error': str(exc), + 'transport': 'ssh', + 'server': None, + 'auth_mode': 'ssh', + 'http_status': None, + 'backup_available': None, + } + + def probe_connection(self, router: Router, global_ssh_key: str | None = None, global_settings=None): + if router.device_type == 'switchos': + return swos_beta_service.probe_router(router, global_settings) + return self._probe_routeros_connection(router, global_ssh_key) + + def _store_connection_result(self, db: Session, router: Router, result: dict): + router.last_connection_status = result['success'] + router.last_connection_tested_at = result['tested_at'] + router.last_connection_error = result.get('error') + router.last_connection_hostname = result.get('hostname') + router.last_connection_model = result.get('model') + router.last_connection_version = result.get('version') + router.last_connection_uptime = result.get('uptime') + router.last_connection_transport = result.get('transport') + router.last_connection_server = result.get('server') + router.last_connection_auth_mode = result.get('auth_mode') + router.last_connection_http_status = result.get('http_status') + router.last_connection_backup_available = result.get('backup_available') + db.add(router) + db.commit() + db.refresh(router) + return result + + def _device_label(self, router: Router) -> str: + platform = 'SwitchOS' if router.device_type == 'switchos' else 'RouterOS' + return f'{platform} device {router.name}' + + def _build_connection_log_message(self, router: Router, result: dict) -> str: + device_label = self._device_label(router) + transport = result.get('transport') or 'unknown transport' + auth_mode = result.get('auth_mode') + http_status = result.get('http_status') + backup_available = result.get('backup_available') + hostname = result.get('hostname') + model = result.get('model') + version = result.get('version') + uptime = result.get('uptime') + server = result.get('server') + + details = [f'via {transport}', f'target={router.host}:{router.port}'] + if router.device_type == 'routeros': + if router.ssh_user: + details.append(f'user={router.ssh_user}') + if hostname: + details.append(f'hostname={hostname}') + if model and model != 'Unknown': + details.append(f'model={model}') + if version and version != 'Unknown': + details.append(f'version={version}') + if uptime and uptime != 'Unknown': + details.append(f'uptime={uptime}') + else: + if auth_mode: + details.append(f'auth={auth_mode}') + if http_status: + details.append(f'http={http_status}') + if server: + details.append(f'server={server}') + if backup_available is not None: + details.append(f'backup_available={"yes" if backup_available else "no"}') + if hostname: + details.append(f'hostname={hostname}') + + detail_suffix = f' ({", ".join(details)})' if details else '' + if result.get('success'): + return f'Connection test OK for {device_label}{detail_suffix}' + + error = result.get('error') or 'Unknown error' + return f'Connection test FAILED for {device_label}{detail_suffix}: {error}' + + def test_connection(self, db: Session, router: Router, global_settings): + result = self.probe_connection(router, global_settings.global_ssh_key, global_settings) + stored_result = self._store_connection_result(db, router, result) + log_service.add(db, self._build_connection_log_message(router, stored_result)) + return stored_result + + +router_service = RouterService() diff --git a/backend/app/services/scheduler.py b/backend/app/services/scheduler.py new file mode 100644 index 0000000..7394790 --- /dev/null +++ b/backend/app/services/scheduler.py @@ -0,0 +1,249 @@ +from __future__ import annotations + +from datetime import datetime, timedelta + +from apscheduler.schedulers.background import BackgroundScheduler + +from app.core.config import settings as app_settings +from app.core.cron_utils import CronValidationError, describe_cron_expression, parse_cron_expression, preview_next_runs +from app.db.session import SessionLocal +from app.models.router import Router +from app.services.backup_service import backup_service +from app.services.log_service import log_service +from app.services.router_service import router_service +from app.services.settings_service import settings_service + + +class SchedulerService: + def __init__(self): + self.scheduler = BackgroundScheduler(timezone=app_settings.timezone) + self.started = False + + def start(self): + if self.started: + return + self.reschedule() + self.scheduler.start() + self.started = True + + def shutdown(self): + if self.started: + self.scheduler.shutdown(wait=False) + self.started = False + + def _parse_cron(self, expr: str): + return parse_cron_expression(expr, app_settings.timezone) + + def validate_cron(self, expr: str): + return self._parse_cron(expr) + + def _interval_next_runs(self, minutes: int, count: int = 3): + now = datetime.now() + return [now + timedelta(minutes=minutes * index) for index in range(1, count + 1)] + + def scheduler_status(self): + with SessionLocal() as db: + settings = settings_service.get_or_create(db) + return { + 'timezone': app_settings.timezone, + 'running': self.started, + 'jobs': [ + self._describe_job( + key='auto_export', + label='settings.schedulerAutoExportLabel', + enabled=settings.enable_auto_export, + cron=settings.export_cron, + ), + self._describe_job( + key='auto_binary', + label='settings.schedulerBinaryLabel', + enabled=bool(settings.binary_cron), + cron=settings.binary_cron, + ), + self._describe_job( + key='retention', + label='settings.schedulerRetentionLabel', + enabled=bool(settings.retention_cron), + cron=settings.retention_cron, + ), + self._describe_interval_job( + key='connection_probe', + label='settings.schedulerConnectionLabel', + minutes=settings.connection_test_interval_minutes, + ), + { + 'key': 'log_cleanup', + 'label': 'settings.schedulerLogsLabel', + 'enabled': True, + 'cron': None, + 'description': 'settings.schedulerLogsDescription', + 'description_params': None, + 'valid': True, + 'next_runs': [], + 'error': None, + }, + ], + } + + def _describe_job(self, key: str, label: str, enabled: bool, cron: str | None): + cron = (cron or '').strip() + if not enabled or not cron: + return { + 'key': key, + 'label': label, + 'enabled': False, + 'cron': cron or None, + 'description': 'settings.scheduleDisabledHint', + 'description_params': None, + 'valid': True, + 'next_runs': [], + 'error': None, + } + try: + next_runs = preview_next_runs(cron, app_settings.timezone, count=3) + return { + 'key': key, + 'label': label, + 'enabled': True, + 'cron': cron, + 'description': 'settings.schedulerCronDescription', + 'description_params': {'description': describe_cron_expression(cron)}, + 'valid': True, + 'next_runs': next_runs, + 'error': None, + } + except CronValidationError as exc: + return { + 'key': key, + 'label': label, + 'enabled': True, + 'cron': cron, + 'description': 'settings.schedulerInvalidCron', + 'description_params': None, + 'valid': False, + 'next_runs': [], + 'error': str(exc), + } + + def _describe_interval_job(self, key: str, label: str, minutes: int): + minutes = int(minutes or 0) + if minutes <= 0: + return { + 'key': key, + 'label': label, + 'enabled': False, + 'cron': None, + 'description': 'settings.connectionTestsDisabledHint', + 'description_params': None, + 'valid': True, + 'next_runs': [], + 'error': None, + } + return { + 'key': key, + 'label': label, + 'enabled': True, + 'cron': None, + 'description': 'settings.connectionTestsEverySummary', + 'description_params': {'minutes': minutes}, + 'valid': True, + 'next_runs': self._interval_next_runs(minutes), + 'error': None, + } + + def reschedule(self): + self.scheduler.remove_all_jobs() + with SessionLocal() as db: + settings = settings_service.get_or_create(db) + job_definitions = [ + ('auto_export', settings.enable_auto_export, settings.export_cron, self._run_auto_export, 'auto export'), + ('auto_binary', bool(settings.binary_cron), settings.binary_cron, self._run_binary_backup, 'binary backup'), + ('retention', bool(settings.retention_cron), settings.retention_cron, self._run_retention, 'retention cleanup'), + ] + + pending_logs: list[str] = [] + for job_id, enabled, cron, callback, label in job_definitions: + cron = (cron or '').strip() + if not enabled or not cron: + continue + try: + trigger = self._parse_cron(cron) + self.scheduler.add_job( + callback, + trigger=trigger, + id=job_id, + replace_existing=True, + coalesce=True, + max_instances=1, + misfire_grace_time=300, + ) + except Exception as exc: + pending_logs.append(f'Scheduler skipped invalid {label} cron ({cron}): {exc}') + + if int(settings.connection_test_interval_minutes or 0) > 0: + self.scheduler.add_job( + self._run_connection_probes, + trigger='interval', + minutes=int(settings.connection_test_interval_minutes), + id='connection_probe', + replace_existing=True, + coalesce=True, + max_instances=1, + misfire_grace_time=300, + ) + + self.scheduler.add_job( + self._run_log_cleanup, + trigger='interval', + days=1, + id='log_cleanup', + replace_existing=True, + coalesce=True, + max_instances=1, + misfire_grace_time=300, + ) + + for message in pending_logs: + log_service.add(db, message, commit=False) + if pending_logs: + db.commit() + + def _run_auto_export(self): + with SessionLocal() as db: + routers = db.query(Router).all() + for router in routers: + try: + backup_service.export_router(db, type('U', (), {'id': router.owner_id})(), router.id) + except Exception as exc: + log_service.add(db, f'Scheduled export failed for {router.name}: {exc}') + + def _run_binary_backup(self): + with SessionLocal() as db: + routers = db.query(Router).all() + for router in routers: + try: + backup_service.binary_backup(db, type('U', (), {'id': router.owner_id})(), router.id) + except Exception as exc: + log_service.add(db, f'Scheduled binary backup failed for {router.name}: {exc}') + + def _run_retention(self): + with SessionLocal() as db: + backup_service.cleanup_old_backups(db) + + def _run_connection_probes(self): + with SessionLocal() as db: + settings = settings_service.get_or_create(db) + routers = db.query(Router).all() + for router in routers: + result = router_service.test_connection(db, router, settings.global_ssh_key) + if not result['success']: + log_service.add(db, f'Scheduled connection test failed for {router.name}: {result.get("error") or "Unknown error"}') + + def _run_log_cleanup(self): + with SessionLocal() as db: + settings = settings_service.get_or_create(db) + deleted = log_service.delete_older_than(db, settings.log_retention_days) + log_service.add(db, f'Log retention cleanup removed {deleted} entries older than {settings.log_retention_days} days') + + +scheduler_service = SchedulerService() diff --git a/backend/app/services/settings_service.py b/backend/app/services/settings_service.py new file mode 100644 index 0000000..7ef52e3 --- /dev/null +++ b/backend/app/services/settings_service.py @@ -0,0 +1,32 @@ +from sqlalchemy.orm import Session + +from app.models.settings import GlobalSettings +from app.schemas.settings import SettingsUpdate + + +class SettingsService: + def get_or_create(self, db: Session) -> GlobalSettings: + settings = db.query(GlobalSettings).first() + if not settings: + settings = GlobalSettings() + db.add(settings) + db.commit() + db.refresh(settings) + return settings + + def update(self, db: Session, payload: SettingsUpdate) -> GlobalSettings: + settings = self.get_or_create(db) + data = payload.model_dump(exclude={'clear_global_ssh_key'}) + for key, value in data.items(): + if key == 'global_ssh_key' and value is None and not payload.clear_global_ssh_key: + continue + setattr(settings, key, value) + if payload.clear_global_ssh_key: + settings.global_ssh_key = None + db.add(settings) + db.commit() + db.refresh(settings) + return settings + + +settings_service = SettingsService() diff --git a/backend/app/services/swos_beta_service.py b/backend/app/services/swos_beta_service.py new file mode 100644 index 0000000..97273c3 --- /dev/null +++ b/backend/app/services/swos_beta_service.py @@ -0,0 +1,174 @@ +import re +from dataclasses import dataclass +from datetime import datetime +from urllib.parse import urlparse + +import requests +from requests.auth import HTTPBasicAuth, HTTPDigestAuth + +from app.schemas.swos_beta import SwosBetaCredentials, SwosBetaProbeResponse + + +@dataclass +class DownloadedSwosBackup: + filename: str + content: bytes + content_type: str + auth_mode: str + base_url: str + + +class SwosBetaService: + timeout_seconds = 12 + + def probe(self, payload: SwosBetaCredentials) -> SwosBetaProbeResponse: + base_url = self._build_base_url(payload.host, payload.port) + response, auth_mode = self._request_with_fallback('GET', base_url, payload) + html = response.text if 'text' in (response.headers.get('content-type') or '').lower() else '' + title = self._extract_title(html) + + backup_response, _ = self._request_with_fallback('GET', f'{base_url}/backup.swb', payload, allow_text_fallback=False) + backup_ok = backup_response.status_code == 200 and len(backup_response.content) > 0 + + return SwosBetaProbeResponse( + success=response.ok, + base_url=base_url, + status_code=response.status_code, + auth_mode=auth_mode, + page_title=title, + content_type=response.headers.get('content-type'), + server=response.headers.get('server'), + save_backup_visible='save backup' in html.lower(), + backup_endpoint_ok=backup_ok, + note='SwitchOS jest obsługiwany bezpośrednio w liście urządzeń.' + ) + + def probe_router(self, router, global_settings) -> dict: + payload = self.credentials_from_router(router, global_settings) + tested_at = datetime.utcnow() + try: + result = self.probe(payload) + return { + 'success': result.success, + 'tested_at': tested_at, + 'model': 'SwitchOS', + 'uptime': f'HTTP {result.status_code}', + 'hostname': result.page_title or router.name, + 'version': None, + 'error': None, + 'transport': 'http', + 'server': result.server, + 'auth_mode': result.auth_mode, + 'http_status': str(result.status_code), + 'backup_available': result.backup_endpoint_ok, + } + except Exception as exc: + return { + 'success': False, + 'tested_at': tested_at, + 'model': 'SwitchOS', + 'uptime': 'HTTP', + 'hostname': router.name, + 'version': None, + 'error': str(exc), + 'transport': 'http', + 'server': None, + 'auth_mode': None, + 'http_status': None, + 'backup_available': None, + } + + def credentials_from_router(self, router, global_settings) -> SwosBetaCredentials: + username = (getattr(router, 'ssh_user', None) or '').strip() or (getattr(global_settings, 'default_switchos_username', None) or '').strip() + password = (getattr(router, 'ssh_password', None) or '').strip() or (getattr(global_settings, 'default_switchos_password', None) or '').strip() + if not username: + raise ValueError('Brak użytkownika SwitchOS. Ustaw dane w urządzeniu albo w ustawieniach globalnych.') + return SwosBetaCredentials( + host=router.host, + port=router.port or 80, + username=username, + password=password, + label=router.name, + ) + + def download_backup(self, payload: SwosBetaCredentials) -> DownloadedSwosBackup: + base_url = self._build_base_url(payload.host, payload.port) + response, auth_mode = self._request_with_fallback('GET', f'{base_url}/backup.swb', payload, allow_text_fallback=False) + if response.status_code != 200: + raise ValueError(f'Urządzenie zwróciło kod HTTP {response.status_code} dla /backup.swb.') + if not response.content: + raise ValueError('Urządzenie zwróciło pusty plik backupu.') + + filename = self._build_filename(payload) + content_type = response.headers.get('content-type') or 'application/octet-stream' + return DownloadedSwosBackup( + filename=filename, + content=response.content, + content_type=content_type, + auth_mode=auth_mode, + base_url=base_url, + ) + + def download_backup_for_router(self, router, global_settings) -> DownloadedSwosBackup: + return self.download_backup(self.credentials_from_router(router, global_settings)) + + def _request_with_fallback(self, method: str, url: str, payload: SwosBetaCredentials, allow_text_fallback: bool = True): + attempts = [] + auth_variants = [ + ('digest', HTTPDigestAuth(payload.username, payload.password)), + ('basic', HTTPBasicAuth(payload.username, payload.password)), + ] + if allow_text_fallback: + auth_variants.append(('none', None)) + + last_response = None + for label, auth in auth_variants: + try: + response = requests.request( + method, + url, + auth=auth, + timeout=self.timeout_seconds, + allow_redirects=True, + ) + last_response = response + if response.status_code < 400: + return response, label + attempts.append(f'{label}:{response.status_code}') + except requests.RequestException as exc: + attempts.append(f'{label}:{exc.__class__.__name__}') + + if last_response is not None: + raise ValueError(f'Nie udało się połączyć ze SwitchOS ({", ".join(attempts)}).') + raise ValueError('Nie udało się połączyć ze SwitchOS.') + + def _build_base_url(self, host: str, port: int) -> str: + raw = host.strip() + parsed = urlparse(raw if '://' in raw else f'http://{raw}') + scheme = parsed.scheme or 'http' + if scheme not in {'http', 'https'}: + raise ValueError('Dozwolone są tylko adresy HTTP lub HTTPS.') + if not parsed.hostname: + raise ValueError('Nieprawidłowy adres hosta.') + resolved_port = parsed.port or port + base = f'{scheme}://{parsed.hostname}' + if resolved_port not in {80, 443} or (scheme == 'http' and resolved_port != 80) or (scheme == 'https' and resolved_port != 443): + base = f'{base}:{resolved_port}' + return base.rstrip('/') + + def _extract_title(self, html: str) -> str | None: + if not html: + return None + match = re.search(r'(.*?)', html, flags=re.IGNORECASE | re.DOTALL) + if not match: + return None + return re.sub(r'\s+', ' ', match.group(1)).strip() or None + + def _build_filename(self, payload: SwosBetaCredentials) -> str: + label = payload.label or payload.host + safe = re.sub(r'[^A-Za-z0-9._-]+', '-', label).strip('-') or 'switchos' + timestamp = datetime.now().strftime('%Y%m%d-%H%M%S') + return f'{safe}-switchos-{timestamp}.swb' + + +swos_beta_service = SwosBetaService() diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..89bc6e1 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,14 @@ +fastapi[standard]==0.135.2 +sqlalchemy==2.0.49 +pydantic==2.12.5 +pydantic-settings==2.13.1 +python-jose[cryptography]==3.5.0 +passlib==1.7.4 +python-multipart==0.0.20 +paramiko==3.5.1 +apscheduler==3.11.0 +requests==2.32.3 +alembic==1.15.2 +email-validator==2.2.0 +pytest==8.3.5 +httpx==0.28.1 diff --git a/backend/scripts/migrate_legacy_sqlite.py b/backend/scripts/migrate_legacy_sqlite.py new file mode 100755 index 0000000..1014345 --- /dev/null +++ b/backend/scripts/migrate_legacy_sqlite.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Import data from the original Flask SQLite database into the new schema. + +Usage: + python backend/scripts/migrate_legacy_sqlite.py /path/to/backup_routeros.db +""" + +from __future__ import annotations + +import sqlite3 +import sys +from datetime import datetime +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) + +from app.core.security import get_password_hash # noqa: E402 +from app.db.session import SessionLocal, init_db # noqa: E402 +from app.models.backup import Backup # noqa: E402 +from app.models.log import OperationLog # noqa: E402 +from app.models.router import Router # noqa: E402 +from app.models.settings import GlobalSettings # noqa: E402 +from app.models.user import User # noqa: E402 + + +def parse_dt(value): + if not value: + return None + for fmt in ("%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S"): + try: + return datetime.strptime(value, fmt) + except ValueError: + pass + return None + + +def main() -> int: + if len(sys.argv) != 2: + print("Usage: python backend/scripts/migrate_legacy_sqlite.py /path/to/legacy.db") + return 1 + + source_path = Path(sys.argv[1]).resolve() + if not source_path.exists(): + print(f"Legacy DB not found: {source_path}") + return 1 + + init_db() + source = sqlite3.connect(str(source_path)) + source.row_factory = sqlite3.Row + dest = SessionLocal() + + try: + user_map: dict[int, int] = {} + for row in source.execute("SELECT id, username, password_hash FROM users ORDER BY id"): + existing = dest.query(User).filter(User.username == row["username"]).first() + if existing: + user_map[row["id"]] = existing.id + continue + user = User(username=row["username"], password_hash=row["password_hash"] or get_password_hash("admin")) + dest.add(user) + dest.flush() + user_map[row["id"]] = user.id + + router_map: dict[int, int] = {} + for row in source.execute( + "SELECT id, owner_id, name, host, port, ssh_user, ssh_key, ssh_password, created_at FROM routers ORDER BY id" + ): + router = Router( + owner_id=user_map.get(row["owner_id"], next(iter(user_map.values()), 1)), + name=row["name"], + host=row["host"], + port=row["port"] or 22, + ssh_user=row["ssh_user"] or "admin", + ssh_key=row["ssh_key"], + ssh_password=row["ssh_password"], + created_at=parse_dt(row["created_at"]), + ) + dest.add(router) + dest.flush() + router_map[row["id"]] = router.id + + for row in source.execute( + "SELECT router_id, file_path, backup_type, created_at, checksum FROM backups ORDER BY id" + ): + file_name = Path(row["file_path"] or "backup").name + backup = Backup( + router_id=router_map[row["router_id"]], + file_path=row["file_path"], + file_name=file_name, + backup_type=row["backup_type"] or "export", + created_at=parse_dt(row["created_at"]), + checksum=row["checksum"], + ) + dest.add(backup) + + for row in source.execute("SELECT message, timestamp FROM operation_logs ORDER BY id"): + dest.add(OperationLog(message=row["message"], timestamp=parse_dt(row["timestamp"]))) + + legacy_settings = source.execute("SELECT * FROM global_settings ORDER BY id LIMIT 1").fetchone() + if legacy_settings: + settings = dest.query(GlobalSettings).first() or GlobalSettings() + for key in legacy_settings.keys(): + if hasattr(settings, key): + setattr(settings, key, legacy_settings[key]) + dest.add(settings) + + dest.commit() + print("Migration completed") + return 0 + finally: + source.close() + dest.close() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/backend/tests/test_auth.py b/backend/tests/test_auth.py new file mode 100644 index 0000000..6840603 --- /dev/null +++ b/backend/tests/test_auth.py @@ -0,0 +1,35 @@ +from fastapi.testclient import TestClient + +from app.main import app + + +def test_login_accepts_form_and_json(monkeypatch, tmp_path): + monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path / 'auth.db'}") + monkeypatch.setenv("DATA_DIR", str(tmp_path / 'data')) + monkeypatch.setenv("SECRET_KEY", "test-secret") + monkeypatch.setenv("DEFAULT_ADMIN_USERNAME", "admin") + monkeypatch.setenv("DEFAULT_ADMIN_PASSWORD", "admin") + + with TestClient(app) as client: + form_response = client.post("/api/auth/login", data={"username": "admin", "password": "admin"}) + assert form_response.status_code == 200 + assert "access_token" in form_response.json() + + json_response = client.post("/api/auth/login", json={"username": "admin", "password": "admin"}) + assert json_response.status_code == 200 + assert "access_token" in json_response.json() + + +def test_auth_me(monkeypatch, tmp_path): + monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path / 'me.db'}") + monkeypatch.setenv("DATA_DIR", str(tmp_path / 'data')) + monkeypatch.setenv("SECRET_KEY", "test-secret") + monkeypatch.setenv("DEFAULT_ADMIN_USERNAME", "admin") + monkeypatch.setenv("DEFAULT_ADMIN_PASSWORD", "admin") + + with TestClient(app) as client: + login_response = client.post("/api/auth/login", data={"username": "admin", "password": "admin"}) + token = login_response.json()["access_token"] + me_response = client.get("/api/auth/me", headers={"Authorization": f"Bearer {token}"}) + assert me_response.status_code == 200 + assert me_response.json()["username"] == "admin" diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py new file mode 100644 index 0000000..7d15b5a --- /dev/null +++ b/backend/tests/test_health.py @@ -0,0 +1,10 @@ +from fastapi.testclient import TestClient + +from app.main import app + + +def test_health_endpoint(): + client = TestClient(app) + response = client.get("/api/health") + assert response.status_code == 200 + assert response.json()["status"] in {"ok", "error"} diff --git a/backend/tests/test_routeros_logging_and_files_filters.py b/backend/tests/test_routeros_logging_and_files_filters.py new file mode 100644 index 0000000..f71e614 --- /dev/null +++ b/backend/tests/test_routeros_logging_and_files_filters.py @@ -0,0 +1,125 @@ +from datetime import datetime + +from app.db.session import SessionLocal +from app.models.backup import Backup + +from fastapi.testclient import TestClient + +from app.main import app + + +def _login(client: TestClient) -> dict[str, str]: + response = client.post('/api/auth/login', data={'username': 'admin', 'password': 'admin'}) + token = response.json()['access_token'] + return {'Authorization': f'Bearer {token}'} + + +def test_routeros_connection_test_creates_verbose_operation_log(monkeypatch): + from app.services import router_service as router_service_module + + monkeypatch.setattr( + router_service_module.router_service, + 'probe_connection', + lambda router, global_ssh_key=None, global_settings=None: { + 'success': True, + 'tested_at': datetime(2026, 4, 13, 10, 30, 0), + 'model': 'RB5009UG+S+', + 'uptime': '1d2h', + 'hostname': 'rb5009-core', + 'version': '7.18.2', + 'error': None, + 'transport': 'ssh', + 'server': None, + 'auth_mode': 'ssh', + 'http_status': None, + 'backup_available': None, + }, + ) + + with TestClient(app) as client: + headers = _login(client) + create_response = client.post( + '/api/routers', + json={ + 'name': 'core01', + 'device_type': 'routeros', + 'host': '10.10.10.1', + 'port': 2222, + 'ssh_user': 'backup', + 'ssh_password': 'secret', + 'ssh_key': None, + }, + headers=headers, + ) + assert create_response.status_code == 200 + device_id = create_response.json()['id'] + + response = client.get(f'/api/routers/{device_id}/test-connection', headers=headers) + assert response.status_code == 200 + + logs_response = client.get('/api/logs', headers=headers) + assert logs_response.status_code == 200 + assert any( + 'Connection test OK for RouterOS device core01' in item['message'] + and 'via ssh' in item['message'] + and 'target=10.10.10.1:2222' in item['message'] + and 'user=backup' in item['message'] + and 'hostname=rb5009-core' in item['message'] + and 'model=RB5009UG+S+' in item['message'] + and 'version=7.18.2' in item['message'] + and 'uptime=1d2h' in item['message'] + for item in logs_response.json() + ) + + +def test_files_endpoint_filters_backups_by_created_on_date(monkeypatch): + from app.services import router_service as router_service_module + + monkeypatch.setattr( + router_service_module.router_service, + 'export', + lambda router, global_ssh_key=None: f'/system identity set name={router.name}', + ) + + with TestClient(app) as client: + headers = _login(client) + create_response = client.post( + '/api/routers', + json={ + 'name': 'archive01', + 'device_type': 'routeros', + 'host': '10.10.10.2', + 'port': 22, + 'ssh_user': 'admin', + 'ssh_password': 'secret', + 'ssh_key': None, + }, + headers=headers, + ) + assert create_response.status_code == 200 + device_id = create_response.json()['id'] + + first = client.post(f'/api/backups/router/{device_id}/export', headers=headers) + second = client.post(f'/api/backups/router/{device_id}/export', headers=headers) + assert first.status_code == 200 + assert second.status_code == 200 + + with SessionLocal() as db: + first_backup = db.query(Backup).filter(Backup.id == first.json()['id']).first() + second_backup = db.query(Backup).filter(Backup.id == second.json()['id']).first() + first_backup.created_at = datetime(2026, 4, 12, 9, 15, 0) + second_backup.created_at = datetime(2026, 4, 13, 11, 45, 0) + db.add(first_backup) + db.add(second_backup) + db.commit() + + filtered = client.get(f'/api/backups?router_id={device_id}&created_on=2026-04-13', headers=headers) + assert filtered.status_code == 200 + payload = filtered.json() + assert len(payload) == 1 + assert payload[0]['created_at'].startswith('2026-04-13T11:45:00') + + previous_day = client.get(f'/api/backups?router_id={device_id}&created_on=2026-04-12', headers=headers) + assert previous_day.status_code == 200 + assert len(previous_day.json()) == 1 + assert previous_day.json()[0]['created_at'].startswith('2026-04-12T09:15:00') diff --git a/backend/tests/test_routers.py b/backend/tests/test_routers.py new file mode 100644 index 0000000..567c8e6 --- /dev/null +++ b/backend/tests/test_routers.py @@ -0,0 +1,62 @@ +from fastapi.testclient import TestClient + +from app.main import app + + +def test_router_list_marks_global_ssh_key_usage(monkeypatch, tmp_path): + monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path / 'routers.db'}") + monkeypatch.setenv("DATA_DIR", str(tmp_path / 'data')) + monkeypatch.setenv("SECRET_KEY", "test-secret") + monkeypatch.setenv("DEFAULT_ADMIN_USERNAME", "admin") + monkeypatch.setenv("DEFAULT_ADMIN_PASSWORD", "admin") + + with TestClient(app) as client: + login_response = client.post("/api/auth/login", data={"username": "admin", "password": "admin"}) + token = login_response.json()["access_token"] + headers = {"Authorization": f"Bearer {token}"} + + settings_response = client.put( + "/api/settings", + json={ + "backup_retention_days": 7, + "log_retention_days": 7, + "export_cron": "", + "binary_cron": "", + "retention_cron": "", + "enable_auto_export": False, + "connection_test_interval_minutes": 0, + "global_ssh_key": "-----BEGIN OPENSSH PRIVATE KEY-----\nabc\n-----END OPENSSH PRIVATE KEY-----", + "pushover_token": None, + "pushover_userkey": None, + "notify_failures_only": True, + "smtp_host": None, + "smtp_port": 587, + "smtp_login": None, + "smtp_password": None, + "smtp_notifications_enabled": False, + "recipient_email": None, + "clear_global_ssh_key": False + }, + headers=headers, + ) + assert settings_response.status_code == 200 + + create_response = client.post( + "/api/routers", + json={ + "name": "edge01", + "host": "10.0.0.1", + "port": 22, + "ssh_user": "admin", + "ssh_password": None, + "ssh_key": None + }, + headers=headers, + ) + assert create_response.status_code == 200 + + list_response = client.get("/api/routers", headers=headers) + assert list_response.status_code == 200 + payload = list_response.json() + assert payload[0]["uses_global_ssh_key"] is True + assert payload[0]["has_effective_ssh_key"] is True diff --git a/backend/tests/test_scheduler.py b/backend/tests/test_scheduler.py new file mode 100644 index 0000000..1705c6d --- /dev/null +++ b/backend/tests/test_scheduler.py @@ -0,0 +1,24 @@ +from app.core.cron_utils import CronValidationError, describe_cron_expression, preview_next_runs, validate_cron_expression + + +def test_validate_cron_expression_accepts_daily_schedule(): + validate_cron_expression('15 2 * * *', 'Europe/Warsaw') + + +def test_validate_cron_expression_rejects_invalid_schedule(): + try: + validate_cron_expression('bad cron', 'Europe/Warsaw') + except CronValidationError: + assert True + return + assert False, 'invalid cron should raise' + + +def test_preview_next_runs_returns_future_datetimes(): + runs = preview_next_runs('0 3 * * 1', 'Europe/Warsaw', count=2) + assert len(runs) == 2 + assert runs[0] < runs[1] + + +def test_describe_cron_expression_humanizes_common_patterns(): + assert describe_cron_expression('0 2 * * *') == 'Every day at 02:00' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ea2607f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +services: + backend: + build: + context: . + dockerfile: backend/Dockerfile + container_name: routeros-backup-backend + env_file: + - .env + volumes: + - ./docker-data:/app/storage + healthcheck: + test: ['CMD', 'curl', '-fsS', 'http://localhost:8000/api/health'] + interval: 30s + timeout: 5s + retries: 5 + start_period: 20s + restart: unless-stopped + + frontend: + build: + context: . + dockerfile: frontend/Dockerfile + container_name: routeros-backup-frontend + restart: unless-stopped + + reverse-proxy: + build: + context: . + dockerfile: reverse-proxy/Dockerfile + container_name: routeros-backup-reverse-proxy + depends_on: + backend: + condition: service_healthy + frontend: + condition: service_started + ports: + - '${APP_PORT:-8080}:80' + restart: unless-stopped diff --git a/env.example b/env.example new file mode 100644 index 0000000..6e10c2e --- /dev/null +++ b/env.example @@ -0,0 +1,9 @@ +SECRET_KEY=change-me-before-production +ACCESS_TOKEN_EXPIRE_MINUTES=7899999 +ALLOW_REGISTRATION=true +DEFAULT_ADMIN_USERNAME=admin +DEFAULT_ADMIN_PASSWORD=admin +APP_PORT=5580 +API_PREFIX=/api +DATA_DIR=/app/storage +DATABASE_URL=sqlite:////app/storage/routeros_backup_next.db \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..142d644 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,12 @@ +FROM node:22-alpine AS build +WORKDIR /app +COPY frontend/package*.json /app/ +RUN npm ci || npm install +COPY frontend /app +RUN npm run build:prod + +FROM nginx:mainline +COPY frontend/nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist/routeros-backup-manager-next-ui/browser /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/angular.json b/frontend/angular.json new file mode 100644 index 0000000..2cb08b7 --- /dev/null +++ b/frontend/angular.json @@ -0,0 +1,75 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "projects": { + "mikrotik-backup-system-ui": { + "projectType": "application", + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/mikrotik-backup-system-ui", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "node_modules/primeicons/primeicons.css", + "src/styles.css" + ] + }, + "configurations": { + "production": { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractLicenses": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "3mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "10kb", + "maximumError": "20kb" + } + ] + }, + "development": { + "optimization": false, + "sourceMap": true, + "extractLicenses": false + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "buildTarget": "mikrotik-backup-system-ui:build" + }, + "configurations": { + "production": { + "buildTarget": "mikrotik-backup-system-ui:build:production" + }, + "development": { + "buildTarget": "mikrotik-backup-system-ui:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/frontend/nginx/default.conf b/frontend/nginx/default.conf new file mode 100644 index 0000000..9fa0d80 --- /dev/null +++ b/frontend/nginx/default.conf @@ -0,0 +1,14 @@ +server { + listen 80; + server_name _; + + server_tokens off; + etag off; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..5a90f23 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,15080 @@ +{ + "name": "mikrotik-backup-system-ui", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mikrotik-backup-system-ui", + "version": "1.0.0", + "dependencies": { + "@angular/animations": "^20.3.0", + "@angular/common": "^20.3.0", + "@angular/compiler": "^20.3.0", + "@angular/core": "^20.3.0", + "@angular/forms": "^20.3.0", + "@angular/platform-browser": "^20.3.0", + "@angular/platform-browser-dynamic": "^20.3.0", + "@angular/router": "^20.3.0", + "@ngx-translate/core": "^17.0.0", + "@ngx-translate/http-loader": "^17.0.0", + "@primeuix/themes": "^1.2.3", + "primeicons": "^7.0.0", + "primeng": "^20.1.2", + "rxjs": "^7.8.1", + "tslib": "^2.8.0", + "zone.js": "~0.15.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^20.3.0", + "@angular/cli": "^20.3.0", + "@angular/compiler-cli": "^20.3.0", + "ansi-colors": "^4.1.3", + "esbuild": "^0.25.0", + "semver": "^7.7.1", + "tree-kill": "^1.2.2", + "typescript": "~5.8.0" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.1.0.tgz", + "integrity": "sha512-sEyWjw28a/9iluA37KLGu8vjxEIlb60uxznfTUmXImy7H5NvbpSO6yYgmgH5KiD7j+zTUUihiST0jEP12IoXow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.35.0.tgz", + "integrity": "sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.35.0.tgz", + "integrity": "sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.35.0.tgz", + "integrity": "sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.35.0.tgz", + "integrity": "sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.35.0.tgz", + "integrity": "sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.35.0.tgz", + "integrity": "sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.35.0.tgz", + "integrity": "sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.35.0.tgz", + "integrity": "sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.35.0.tgz", + "integrity": "sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.35.0.tgz", + "integrity": "sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.35.0.tgz", + "integrity": "sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.35.0.tgz", + "integrity": "sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.35.0.tgz", + "integrity": "sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.2003.23", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.23.tgz", + "integrity": "sha512-o9fzWCxcLcUPxd7xP0gA10cQAwg9kNrS4VHFCjJ7+kB6pi8GSZqqEw/N1BB0s/+zpJ4bQ4EC82hDC6Cu5Fpv9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.3.23", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "20.3.23", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.23.tgz", + "integrity": "sha512-9z1oWmtRR5zaGb9bn3N/TwlMQS50lflpb+1HPujFvNfSAsCuFLQhxPxxv37o1g+06SPDXqldfxTBDk4LcYQgxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2003.23", + "@angular-devkit/build-webpack": "0.2003.23", + "@angular-devkit/core": "20.3.23", + "@angular/build": "20.3.23", + "@babel/core": "7.28.3", + "@babel/generator": "7.28.3", + "@babel/helper-annotate-as-pure": "7.27.3", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-transform-async-generator-functions": "7.28.0", + "@babel/plugin-transform-async-to-generator": "7.27.1", + "@babel/plugin-transform-runtime": "7.28.3", + "@babel/preset-env": "7.28.3", + "@babel/runtime": "7.28.3", + "@discoveryjs/json-ext": "0.6.3", + "@ngtools/webpack": "20.3.23", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.21", + "babel-loader": "10.0.0", + "browserslist": "^4.21.5", + "copy-webpack-plugin": "14.0.0", + "css-loader": "7.1.2", + "esbuild-wasm": "0.25.9", + "fast-glob": "3.3.3", + "http-proxy-middleware": "3.0.5", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "karma-source-map-support": "1.4.0", + "less": "4.4.0", + "less-loader": "12.3.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.3.1", + "mini-css-extract-plugin": "2.9.4", + "open": "10.2.0", + "ora": "8.2.0", + "picomatch": "4.0.4", + "piscina": "5.1.3", + "postcss": "8.5.6", + "postcss-loader": "8.1.1", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.2", + "sass": "1.90.0", + "sass-loader": "16.0.5", + "semver": "7.7.2", + "source-map-loader": "5.0.0", + "source-map-support": "0.5.21", + "terser": "5.43.1", + "tree-kill": "1.2.2", + "tslib": "2.8.1", + "webpack": "5.105.0", + "webpack-dev-middleware": "7.4.2", + "webpack-dev-server": "5.2.2", + "webpack-merge": "6.0.1", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.25.9" + }, + "peerDependencies": { + "@angular/compiler-cli": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/localize": "^20.0.0", + "@angular/platform-browser": "^20.0.0", + "@angular/platform-server": "^20.0.0", + "@angular/service-worker": "^20.0.0", + "@angular/ssr": "^20.3.23", + "@web/test-runner": "^0.20.0", + "browser-sync": "^3.0.2", + "jest": "^29.5.0 || ^30.2.0", + "jest-environment-jsdom": "^29.5.0 || ^30.2.0", + "karma": "^6.3.0", + "ng-packagr": "^20.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "typescript": ">=5.8 <6.0" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "@web/test-runner": { + "optional": true + }, + "browser-sync": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.2003.23", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.23.tgz", + "integrity": "sha512-6razXNguBr7VSl4DA3ch6u+VnROe9DaW74lIAbo3CdfsYN7VoZxe9rpx0x4OaoL08OBcANlrVIfii0iHswMUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2003.23", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" + } + }, + "node_modules/@angular-devkit/core": { + "version": "20.3.23", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.23.tgz", + "integrity": "sha512-NOcoT8FMXHAiqvfTb5LCmT7/mtsYwQ+p5a49bo2uWYeKdSwniAdGGR+7yDxLdYXJpe8dc/epo/uiAq/coi+7YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "20.3.23", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.23.tgz", + "integrity": "sha512-GASRHzFcakcdhw7br2Bc9u7SmWNwnWVWTIC9w0MZfDT2QLU3iYpHsHgW84x02PRXJSyaGhIg8jlnD2ebPEP5Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.3.23", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "8.2.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/animations": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.18.tgz", + "integrity": "sha512-XFxgSyjfs0SRD2vQVFJljmM4z9nTvUoI8TRqSre/+l8D2FgzD5pG67Aj2BgDgpSFAUkIcI37G48ijK7a3ZZ3WA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.3.18" + } + }, + "node_modules/@angular/build": { + "version": "20.3.23", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.23.tgz", + "integrity": "sha512-fNUWoTmbOQONorl9pPkzzsHEMlo5k2DHfUWQ9vNj5oNLBA+46gaTkTLCr8qfRxY3nHTwo3pKlAOx5HzrHjOWFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2003.23", + "@babel/core": "7.28.3", + "@babel/helper-annotate-as-pure": "7.27.3", + "@babel/helper-split-export-declaration": "7.24.7", + "@inquirer/confirm": "5.1.14", + "@vitejs/plugin-basic-ssl": "2.1.0", + "beasties": "0.3.5", + "browserslist": "^4.23.0", + "esbuild": "0.25.9", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "listr2": "9.0.1", + "magic-string": "0.30.17", + "mrmime": "2.0.1", + "parse5-html-rewriting-stream": "8.0.0", + "picomatch": "4.0.4", + "piscina": "5.1.3", + "rollup": "4.59.0", + "sass": "1.90.0", + "semver": "7.7.2", + "source-map-support": "0.5.21", + "tinyglobby": "0.2.14", + "vite": "7.3.2", + "watchpack": "2.4.4" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "lmdb": "3.4.2" + }, + "peerDependencies": { + "@angular/compiler": "^20.0.0", + "@angular/compiler-cli": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/localize": "^20.0.0", + "@angular/platform-browser": "^20.0.0", + "@angular/platform-server": "^20.0.0", + "@angular/service-worker": "^20.0.0", + "@angular/ssr": "^20.3.23", + "karma": "^6.4.0", + "less": "^4.2.0", + "ng-packagr": "^20.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.8 <6.0", + "vitest": "^3.1.1" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/@angular/build/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cdk": { + "version": "20.2.14", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz", + "integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==", + "license": "MIT", + "peer": true, + "dependencies": { + "parse5": "^8.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^20.0.0 || ^21.0.0", + "@angular/core": "^20.0.0 || ^21.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "20.3.23", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.23.tgz", + "integrity": "sha512-84Q3sM6owpl6qZfqlb7VZARZOD2C7mma9PGMh9nfn/RW+I31Sx4dOhb9MaGlOT+eASEaQFkQ+SFcRmAKhTIHOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2003.23", + "@angular-devkit/core": "20.3.23", + "@angular-devkit/schematics": "20.3.23", + "@inquirer/prompts": "7.8.2", + "@listr2/prompt-adapter-inquirer": "3.0.1", + "@modelcontextprotocol/sdk": "1.26.0", + "@schematics/angular": "20.3.23", + "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.35.0", + "ini": "5.0.0", + "jsonc-parser": "3.3.1", + "listr2": "9.0.1", + "npm-package-arg": "13.0.0", + "pacote": "21.0.4", + "resolve": "1.22.10", + "semver": "7.7.2", + "yargs": "18.0.0", + "zod": "4.1.13" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/common": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.18.tgz", + "integrity": "sha512-M62oQbSTRmnGavIVCwimoadg/PDWadgNhactMm9fgH0eM9rx+iWBAYJk4VufO0bwOhysFpRZpJgXlFjOifz/Jw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.3.18", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.18.tgz", + "integrity": "sha512-AaP/LCiDNcYmF135EEozjyR04NRBT38ZfBHQwjhgwiBBTejmvcpHwJaHSkraLpZqZzE4BQqqmgiQ1EJqxEwLVA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@angular/compiler-cli": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.18.tgz", + "integrity": "sha512-zsoEgLgnblmRbi47YwMghKirJ8IBKJ3+I8TxLBRIBrhx+KHFp+6oeDeLyu9H+djdyk88zexVd09wzR/YK73F0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.3", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.3.18", + "typescript": ">=5.8 <6.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular/core": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.18.tgz", + "integrity": "sha512-B+NQQngd/aDbcfW0zGLis3wTLDeHTeTYMl/mGKQH+HwdPaRCKI1wEtaXaOYVJXkP2FeThocPevB8gLwNlPQUUw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.3.18", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } + } + }, + "node_modules/@angular/forms": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.18.tgz", + "integrity": "sha512-x6/99LfxolyZIFUL3Wr0OrtuXHEDwEz/rwx+WzE7NL+n35yO40t3kp0Sn5uMFwI94i91QZJmXHltMpZhrVLuYg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.18", + "@angular/core": "20.3.18", + "@angular/platform-browser": "20.3.18", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.18.tgz", + "integrity": "sha512-q6s5rEN1yYazpHYp+k4pboXRzMsRB9auzTRBEhyXSGYxqzrnn3qHN0DqgsLC9WAdyhCgnIEMFA8kRT+W277DqQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/animations": "20.3.18", + "@angular/common": "20.3.18", + "@angular/core": "20.3.18" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.18.tgz", + "integrity": "sha512-NyTobOGYVzGmPmtI+3lxMzxi0TbLq4SRNQ2ENEJAt6k2JnMmHBm483ppLRAM47nGlDdiraW0IX93EtYYNkiK3g==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.18", + "@angular/compiler": "20.3.18", + "@angular/core": "20.3.18", + "@angular/platform-browser": "20.3.18" + } + }, + "node_modules/@angular/router": { + "version": "20.3.18", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.18.tgz", + "integrity": "sha512-3CWejsEYr+ze+ktvWN/qHdyq5WLrj96QZpGYJyxh1pchIcpMPE9MmLpdjf0CUrWYB7g/85u0Geq/xsz72JrGng==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.18", + "@angular/core": "20.3.18", + "@angular/platform-browser": "20.3.18", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "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/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.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, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.2.tgz", + "integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.2.1", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.17", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.1.tgz", + "integrity": "sha512-YrEi/ZPmgc+GfdO0esBF04qv8boK9Dg9WpRQw/+vM8Qt3nnVIJWIa8HwZ/LXVZ0DB11XUROM8El/7yYTJX+WtA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.1.tgz", + "integrity": "sha512-ooEPvSW/HQDivPDPZMibHGKZf/QS4WRir1czGZmXmp3MsQqLECZEpN0JobrD8iV9BzsuwdIv+PxtWX9WpPLsIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.1.tgz", + "integrity": "sha512-3YaKhP8gXEKN+2O49GLNfNb5l2gbnCFHyAaybbA2JkkbQP3dpdef7WcUaHAulg/c5Dg4VncHsA3NWAUSZMR5KQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "@jsonjoy.com/fs-print": "4.57.1", + "@jsonjoy.com/fs-snapshot": "4.57.1", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.1.tgz", + "integrity": "sha512-XHkFKQ5GSH3uxm8c3ZYXVrexGdscpWKIcMWKFQpMpMJc8gA3AwOMBJXJlgpdJqmrhPyQXxaY9nbkNeYpacC0Og==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.1.tgz", + "integrity": "sha512-pqGHyWWzNck4jRfaGV39hkqpY5QjRUQ/nRbNT7FYbBa0xf4bDG+TE1Gt2KWZrSkrkZZDE3qZUjYMbjwSliX6pg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.1.tgz", + "integrity": "sha512-vp+7ZzIB8v43G+GLXTS4oDUSQmhAsRz532QmmWBbdYA20s465JvwhkSFvX9cVTqRRAQg+vZ7zWDaIEh0lFe2gw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.1.tgz", + "integrity": "sha512-Ynct7ZJmfk6qoXDOKfpovNA36ITUx8rChLmRQtW08J73VOiuNsU8PB6d/Xs7fxJC2ohWR3a5AqyjmLojfrw5yw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.57.1", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.1.tgz", + "integrity": "sha512-/oG8xBNFMbDXTq9J7vepSA1kerS5vpgd3p5QZSPd+nX59uwodGJftI51gDYyHRpP57P3WCQf7LHtBYPqwUg2Bg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz", + "integrity": "sha512-3XFmGwm3u6ioREG+ynAQB7FoxfajgQnMhIu8wC5eo/Lsih4aKDg0VuIMGaOsYn7hJSJagSeaD4K8yfpkEoDEmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 8", + "listr2": "9.0.1" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.2.tgz", + "integrity": "sha512-NK80WwDoODyPaSazKbzd3NEJ3ygePrkERilZshxBViBARNz21rmediktGHExoj9n5t9+ChlgLlxecdFKLCuCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.2.tgz", + "integrity": "sha512-zevaowQNmrp3U7Fz1s9pls5aIgpKRsKb3dZWDINtLiozh3jZI9fBrI19lYYBxqdyiIyNdlyiidPnwPShj4aK+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.2.tgz", + "integrity": "sha512-OmHCULY17rkx/RoCoXlzU7LyR8xqrksgdYWwtYa14l/sseezZ8seKWXcogHcjulBddER5NnEFV4L/Jtr2nyxeg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.2.tgz", + "integrity": "sha512-ZBEfbNZdkneebvZs98Lq30jMY8V9IJzckVeigGivV7nTHJc+89Ctomp1kAIWKlwIG0ovCDrFI448GzFPORANYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.2.tgz", + "integrity": "sha512-vL9nM17C77lohPYE4YaAQvfZCSVJSryE4fXdi8M7uWPBnU+9DJabgKVAeyDb84ZM2vcFseoBE4/AagVtJeRE7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.2.tgz", + "integrity": "sha512-SXWjdBfNDze4ZPeLtYIzsIeDJDJ/SdsA0pEXcUBayUIMO0FQBHfVZZyHXQjjHr4cvOAzANBgIiqaXRwfMhzmLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.2.tgz", + "integrity": "sha512-IY+r3bxKW6Q6sIPiMC0L533DEfRJSXibjSI3Ft/w9Q8KQBNqEIvUFXt+09wV8S5BRk0a8uSF19YWxuRwEfI90g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngtools/webpack": { + "version": "20.3.23", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.23.tgz", + "integrity": "sha512-dP1RxzdH4GA7l6LVzJpNb89mXImggtQNJxQidyN6tN9b0Pj28rpo/miHptP6x+/V4EMp36WgnQxSWTHKAeqI9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^20.0.0", + "typescript": ">=5.8 <6.0", + "webpack": "^5.54.0" + } + }, + "node_modules/@ngx-translate/core": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-17.0.0.tgz", + "integrity": "sha512-Rft2D5ns2pq4orLZjEtx1uhNuEBerUdpFUG1IcqtGuipj6SavgB8SkxtNQALNDA+EVlvsNCCjC2ewZVtUeN6rg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=16", + "@angular/core": ">=16" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-17.0.0.tgz", + "integrity": "sha512-hgS8sa0ARjH9ll3PhkLTufeVXNI2DNR2uFKDhBgq13siUXzzVr/a31M6zgecrtwbA34iaBV01hsTMbMS8V7iIw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=16", + "@angular/core": ">=16" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/git/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@primeuix/styled": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.7.4.tgz", + "integrity": "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==", + "license": "MIT", + "dependencies": { + "@primeuix/utils": "^0.6.1" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primeuix/styles": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-1.2.5.tgz", + "integrity": "sha512-nypFRct/oaaBZqP4jinT0puW8ZIfs4u+l/vqUFmJEPU332fl5ePj6DoOpQgTLzo3OfmvSmz5a5/5b4OJJmmi7Q==", + "license": "MIT", + "dependencies": { + "@primeuix/styled": "^0.7.3" + } + }, + "node_modules/@primeuix/themes": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@primeuix/themes/-/themes-1.2.5.tgz", + "integrity": "sha512-n3YkwJrHQaEESc/D/A/iD815sxp8cKnmzscA6a8Tm8YvMtYU32eCahwLLe6h5rywghVwxASWuG36XBgISYOIjQ==", + "license": "MIT", + "dependencies": { + "@primeuix/styled": "^0.7.3" + } + }, + "node_modules/@primeuix/utils": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.6.4.tgz", + "integrity": "sha512-pZ5f+vj7wSzRhC7KoEQRU5fvYAe+RP9+m39CTscZ3UywCD1Y2o6Fe1rRgklMPSkzUcty2jzkA0zMYkiJBD1hgg==", + "license": "MIT", + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "20.3.23", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.23.tgz", + "integrity": "sha512-o9DWt5MZvEe69Byj2EKRNk/Pe7fSihvnvBOnPDzAi86jmMiKwjfa0rsk4vTXiDu4HLw9wJ6IKZ/lQKTfJbVnUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.3.23", + "@angular-devkit/schematics": "20.3.23", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.0.tgz", + "integrity": "sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.1.tgz", + "integrity": "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", + "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/algoliasearch": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.35.0.tgz", + "integrity": "sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.1.0", + "@algolia/client-abtesting": "5.35.0", + "@algolia/client-analytics": "5.35.0", + "@algolia/client-common": "5.35.0", + "@algolia/client-insights": "5.35.0", + "@algolia/client-personalization": "5.35.0", + "@algolia/client-query-suggestions": "5.35.0", + "@algolia/client-search": "5.35.0", + "@algolia/ingestion": "1.35.0", + "@algolia/monitoring": "1.35.0", + "@algolia/recommend": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", + "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": "^18.20.0 || ^20.10.0 || >=22.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5.61.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", + "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/beasties": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", + "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "css-select": "^6.0.0", + "css-what": "^7.0.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "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==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.336", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.336.tgz", + "integrity": "sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.9.tgz", + "integrity": "sha512-Jpv5tCSwQg18aCqCRD3oHIX/prBhXMDapIoG//A+6+dV0e7KQMGFg85ihJ5T1EeMjbZjON3TqFy0VrGAnIHLDA==", + "dev": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", + "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "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.8" + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.12", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", + "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/less": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.4.0.tgz", + "integrity": "sha512-kdTwsyRuncDfjEs0DlRILWNvxhDG/Zij4YLO4TMJgDLW+8OzpfkdPnRgrsRuY1o+oaxJGWsps5f/RVBgGmmN0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.3.0.tgz", + "integrity": "sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "license": "ISC", + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz", + "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lmdb": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.2.tgz", + "integrity": "sha512-nwVGUfTBUwJKXd6lRV8pFNfnrCC1+l49ESJRM19t/tFb/97QfJEixe5DYRvug5JO7DSFKoKaVy7oGMt5rVqZvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.4.2", + "@lmdb/lmdb-darwin-x64": "3.4.2", + "@lmdb/lmdb-linux-arm": "3.4.2", + "@lmdb/lmdb-linux-arm64": "3.4.2", + "@lmdb/lmdb-linux-x64": "3.4.2", + "@lmdb/lmdb-win32-arm64": "3.4.2", + "@lmdb/lmdb-win32-x64": "3.4.2" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-fetch-happen": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", + "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memfs": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.1.tgz", + "integrity": "sha512-WvzrWPwMQT+PtbX2Et64R4qXKK0fj/8pO85MrUCzymX3twwCiJCdvntW3HdhG1teLJcHDDLIKx5+c3HckWYZtQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-fsa": "4.57.1", + "@jsonjoy.com/fs-node": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-to-fsa": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "@jsonjoy.com/fs-print": "4.57.1", + "@jsonjoy.com/fs-snapshot": "4.57.1", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", + "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.9.tgz", + "integrity": "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==", + "dev": true, + "license": "MIT", + "optional": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-package-arg": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.0.tgz", + "integrity": "sha512-+t2etZAGcB7TbbLHfDwooV9ppB2LhhcT6A+L9cahsf9mEUAoQ6CktLEVvEnpD0N5CkX7zJqnPGaFtoQDy9EkHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ordered-binary": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/pacote": { + "version": "21.0.4", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.4.tgz", + "integrity": "sha512-RplP/pDW0NNNDh3pnaoIWYPvNenS7UqMbXyvMqJczosiFWTeGGwJC2NQBLqKf4rGLFfwCOnntw1aEp9Jiqm1MA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/pacote/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "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/parse-json/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", + "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0", + "parse5": "^8.0.0", + "parse5-sax-parser": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", + "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/piscina": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.3.tgz", + "integrity": "sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.4" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/primeicons": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz", + "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==", + "license": "MIT" + }, + "node_modules/primeng": { + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/primeng/-/primeng-20.4.0.tgz", + "integrity": "sha512-vXUD1G4/uet4rDkPW8xx7yZWj7RmsmexEJ3+GhpQgsNaLtPFsTCVfQq8v4FQ4tIs7shoD0hz76d3jtjGWZ49QQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@primeuix/styled": "^0.7.4", + "@primeuix/styles": "^1.2.5", + "@primeuix/utils": "^0.6.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^20.0.4", + "@angular/cdk": "^20.0.3", + "@angular/common": "^20.0.4", + "@angular/core": "^20.0.4", + "@angular/forms": "^20.0.4", + "@angular/platform-browser": "^20.0.4", + "@angular/router": "^20.0.4", + "rxjs": "^6.0.0 || ^7.8.1" + } + }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", + "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-loader": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", + "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/thingies": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-name": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "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 + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/vite/node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/webpack": { + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", + "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.21.2", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "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/webpack-dev-server/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-server/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/webpack-dev-server/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/webpack-dev-server/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/zone.js": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", + "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..37bac53 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,41 @@ +{ + "name": "mikrotik-backup-system-ui", + "version": "1.0.0", + "private": true, + "scripts": { + "start": "ng serve --host 127.0.0.1 --port 4200 --proxy-config proxy.conf.json", + "start:lan": "ng serve --host 0.0.0.0 --port 4200 --proxy-config proxy.conf.json", + "build": "ng build", + "test": "ng test", + "build:prod": "ng build --configuration production", + "build:dev": "ng build --configuration development" + }, + "dependencies": { + "@angular/animations": "^20.3.0", + "@angular/common": "^20.3.0", + "@angular/compiler": "^20.3.0", + "@angular/core": "^20.3.0", + "@angular/forms": "^20.3.0", + "@angular/platform-browser": "^20.3.0", + "@angular/platform-browser-dynamic": "^20.3.0", + "@angular/router": "^20.3.0", + "@ngx-translate/core": "^17.0.0", + "@ngx-translate/http-loader": "^17.0.0", + "primeicons": "^7.0.0", + "primeng": "^20.1.2", + "rxjs": "^7.8.1", + "tslib": "^2.8.0", + "zone.js": "~0.15.0", + "@primeuix/themes": "^1.2.3" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^20.3.0", + "@angular/cli": "^20.3.0", + "@angular/compiler-cli": "^20.3.0", + "typescript": "~5.8.0", + "ansi-colors": "^4.1.3", + "esbuild": "^0.25.0", + "semver": "^7.7.1", + "tree-kill": "^1.2.2" + } +} diff --git a/frontend/proxy.conf.json b/frontend/proxy.conf.json new file mode 100644 index 0000000..c5ba102 --- /dev/null +++ b/frontend/proxy.conf.json @@ -0,0 +1,8 @@ +{ + "/api": { + "target": "http://127.0.0.1:8000", + "secure": false, + "changeOrigin": true, + "logLevel": "info" + } +} diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html new file mode 100644 index 0000000..4b9b417 --- /dev/null +++ b/frontend/src/app/app.component.html @@ -0,0 +1,68 @@ + + + +
+
+ {{ 'footer.apiOfflineTitle' | translate }} + {{ 'footer.apiOfflineMessage' | translate }} +
+ +
+ + +
+
+ + + +
+ + +
+ +
+ +
+ + {{ 'footer.apiLabel' | translate }}: {{ apiStateLabelKey() | translate }} + {{ 'footer.apiLatencyLabel' | translate }}: {{ apiLatencyLabel() }} + {{ 'footer.apiDocs' | translate }} +
+
+
+
+ + +
+
+ +
+ +
+ + {{ 'footer.apiLabel' | translate }}: {{ apiStateLabelKey() | translate }} + {{ 'footer.apiLatencyLabel' | translate }}: {{ apiLatencyLabel() }} + {{ 'footer.apiDocs' | translate }} +
+
+
diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts new file mode 100644 index 0000000..5201797 --- /dev/null +++ b/frontend/src/app/app.component.ts @@ -0,0 +1,141 @@ +import { CommonModule } from '@angular/common'; +import { Component, computed, inject } from '@angular/core'; +import { NavigationEnd, Router, RouterOutlet } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { filter } from 'rxjs'; + +import { AuthService } from './core/services/auth.service'; +import { FontService } from './core/services/font.service'; +import { LayoutService } from './core/services/layout.service'; +import { ThemeService } from './core/services/theme.service'; +import { APP_LANGUAGE_OPTIONS, AppLanguage, LanguageService } from './core/services/language.service'; +import { ApiStatusService } from './core/services/api-status.service'; +import { ToastModule } from 'primeng/toast'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; + +import { AppSidebarComponent } from './shared/layout/app-sidebar.component'; +import { AppTopbarComponent, TopbarLanguageOption } from './shared/layout/app-topbar.component'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [CommonModule, RouterOutlet, TranslateModule, ToastModule, ConfirmDialogModule, AppSidebarComponent, AppTopbarComponent], + templateUrl: './app.component.html' +}) +export class AppComponent { + auth = inject(AuthService); + theme = inject(ThemeService); + language = inject(LanguageService); + font = inject(FontService); + router = inject(Router); + layout = inject(LayoutService); + apiStatus = inject(ApiStatusService); + pageLabel = 'dashboard.title'; + readonly author = 'Mateusz Gruszczyński'; + readonly authorHandle = '@linuxiarz.pl'; + readonly authorUrl = 'https://linuxiarz.pl'; + readonly apiSnapshot = this.apiStatus.snapshot; + readonly languageOptions: TopbarLanguageOption[] = APP_LANGUAGE_OPTIONS; + readonly apiStateLabelKey = computed(() => { + switch (this.apiSnapshot().state) { + case 'online': + return 'footer.apiOnline'; + case 'offline': + return 'footer.apiOffline'; + default: + return 'footer.apiChecking'; + } + }); + readonly apiLatencyLabel = computed(() => { + const latency = this.apiSnapshot().latencyMs; + return latency === null ? '—' : `${latency} ms`; + }); + + readonly menuItems = [ + { label: 'nav.dashboard', link: '/', icon: 'pi pi-home', exact: true }, + { label: 'nav.routers', link: '/devices', icon: 'pi pi-server', exact: false }, + { label: 'nav.files', link: '/files', icon: 'pi pi-folder-open', exact: false }, + { label: 'nav.diffConfigs', link: '/diff-configs', icon: 'pi pi-code', exact: false }, + { label: 'nav.logs', link: '/logs', icon: 'pi pi-history', exact: false }, + { label: 'nav.changePassword', link: '/change-password', icon: 'pi pi-lock', exact: false }, + { label: 'nav.settings', link: '/settings', icon: 'pi pi-cog', exact: false } + ]; + + get currentPageTitle(): string { + return this.pageLabel; + } + + constructor() { + this.language.init(); + this.font.init(); + this.theme.init(); + this.apiStatus.startMonitoring(); + this.auth.restoreSession(); + this.updatePageLabel(this.router.url); + this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe((event) => { + this.updatePageLabel((event as NavigationEnd).urlAfterRedirects); + this.layout.closeMobileSidebar(); + }); + } + + toggleTheme() { + this.theme.toggle(); + } + + changeLanguage(lang: string) { + const nextLanguage = lang as AppLanguage; + const user = this.auth.user(); + if (!user) { + this.language.set(nextLanguage); + return; + } + + this.language.setForAuthenticatedUser(nextLanguage); + this.auth.updatePreferences({ preferred_language: nextLanguage, preferred_font: user.preferred_font }).subscribe(); + } + + logout() { + this.auth.logout(); + this.router.navigate(['/login']); + } + + refreshApiStatus() { + this.apiStatus.probe(); + } + + get apiStatusClass(): string { + return `layout-footer__status--${this.apiSnapshot().state}`; + } + + private updatePageLabel(url: string) { + if (url.startsWith('/devices/') || url.startsWith('/routers/')) { + this.pageLabel = 'routers.detailTitle'; + return; + } + if (url.startsWith('/devices') || url.startsWith('/routers')) { + this.pageLabel = 'routers.title'; + return; + } + if (url.startsWith('/files')) { + this.pageLabel = 'files.title'; + return; + } + if (url.startsWith('/diff-configs')) { + this.pageLabel = 'diffConfigs.title'; + return; + } + if (url.startsWith('/settings')) { + this.pageLabel = 'settings.title'; + return; + } + if (url.startsWith('/logs')) { + this.pageLabel = 'logs.title'; + return; + } + if (url.startsWith('/change-password')) { + this.pageLabel = 'auth.changePassword'; + return; + } + this.pageLabel = 'dashboard.title'; + } +} diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts new file mode 100644 index 0000000..111ff8d --- /dev/null +++ b/frontend/src/app/app.routes.ts @@ -0,0 +1,29 @@ +import { Routes } from '@angular/router'; + +import { authGuard } from './core/guards/auth.guard'; +import { ChangePasswordPageComponent } from './features/auth/change-password-page.component'; +import { LoginPageComponent } from './features/auth/login-page.component'; +import { RegisterPageComponent } from './features/auth/register-page.component'; +import { DashboardPageComponent } from './features/dashboard/dashboard-page.component'; +import { DiffConfigsPageComponent } from './features/diff-configs/diff-configs-page.component'; +import { FilesPageComponent } from './features/files/files-page.component'; +import { LogsPageComponent } from './features/logs/logs-page.component'; +import { RouterDetailPageComponent } from './features/routers/router-detail-page.component'; +import { RoutersPageComponent } from './features/routers/routers-page.component'; +import { SettingsPageComponent } from './features/settings/settings-page.component'; + +export const routes: Routes = [ + { path: 'login', component: LoginPageComponent }, + { path: 'register', component: RegisterPageComponent }, + { path: 'change-password', canActivate: [authGuard], component: ChangePasswordPageComponent }, + { path: '', canActivate: [authGuard], component: DashboardPageComponent }, + { path: 'devices', canActivate: [authGuard], component: RoutersPageComponent }, + { path: 'devices/:id', canActivate: [authGuard], component: RouterDetailPageComponent }, + { path: 'routers', redirectTo: 'devices', pathMatch: 'full' }, + { path: 'routers/:id', redirectTo: 'devices/:id', pathMatch: 'full' }, + { path: 'files', canActivate: [authGuard], component: FilesPageComponent }, + { path: 'diff-configs', canActivate: [authGuard], component: DiffConfigsPageComponent }, + { path: 'settings', canActivate: [authGuard], component: SettingsPageComponent }, + { path: 'logs', canActivate: [authGuard], component: LogsPageComponent }, + { path: '**', redirectTo: '' } +]; diff --git a/frontend/src/app/core/guards/auth.guard.ts b/frontend/src/app/core/guards/auth.guard.ts new file mode 100644 index 0000000..254777b --- /dev/null +++ b/frontend/src/app/core/guards/auth.guard.ts @@ -0,0 +1,14 @@ +import { inject } from '@angular/core'; +import { Router } from '@angular/router'; + +import { AuthService } from '../services/auth.service'; + +export const authGuard = () => { + const auth = inject(AuthService); + const router = inject(Router); + if (!auth.isLoggedIn()) { + router.navigate(['/login']); + return false; + } + return true; +}; diff --git a/frontend/src/app/core/guards/guest.guard.ts b/frontend/src/app/core/guards/guest.guard.ts new file mode 100644 index 0000000..438502d --- /dev/null +++ b/frontend/src/app/core/guards/guest.guard.ts @@ -0,0 +1,15 @@ +import { inject } from '@angular/core'; +import { Router } from '@angular/router'; + +import { AuthService } from '../services/auth.service'; + +export const guestGuard = () => { + const auth = inject(AuthService); + const router = inject(Router); + + if (auth.isLoggedIn()) { + return router.createUrlTree(['/']); + } + + return true; +}; diff --git a/frontend/src/app/core/interceptors/auth.interceptor.ts b/frontend/src/app/core/interceptors/auth.interceptor.ts new file mode 100644 index 0000000..34ba8c3 --- /dev/null +++ b/frontend/src/app/core/interceptors/auth.interceptor.ts @@ -0,0 +1,7 @@ +import { HttpInterceptorFn } from '@angular/common/http'; + +export const authInterceptor: HttpInterceptorFn = (req, next) => { + const token = localStorage.getItem('routeros_token'); + if (!token) return next(req); + return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })); +}; diff --git a/frontend/src/app/core/services/api-status.service.ts b/frontend/src/app/core/services/api-status.service.ts new file mode 100644 index 0000000..3bdae2b --- /dev/null +++ b/frontend/src/app/core/services/api-status.service.ts @@ -0,0 +1,83 @@ +import { Injectable, inject, signal } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { catchError, fromEvent, interval, merge, of, Subscription, timeout } from 'rxjs'; + +type ApiConnectionState = 'checking' | 'online' | 'offline'; + +interface HealthResponse { + status?: string; + timestamp?: string; +} + +export interface ApiStatusSnapshot { + state: ApiConnectionState; + latencyMs: number | null; + checkedAt: string | null; +} + +@Injectable({ providedIn: 'root' }) +export class ApiStatusService { + private readonly http = inject(HttpClient); + private started = false; + private inFlight = false; + private pollSubscription?: Subscription; + private offlineSubscription?: Subscription; + + readonly snapshot = signal({ + state: 'checking', + latencyMs: null, + checkedAt: null + }); + + startMonitoring() { + if (this.started || typeof window === 'undefined') { + return; + } + this.started = true; + + this.pollSubscription = merge(of(0), interval(15000), fromEvent(window, 'focus'), fromEvent(window, 'online')).subscribe(() => { + this.probe(); + }); + + this.offlineSubscription = fromEvent(window, 'offline').subscribe(() => { + this.snapshot.set({ + state: 'offline', + latencyMs: null, + checkedAt: new Date().toISOString() + }); + }); + } + + probe() { + if (this.inFlight) { + return; + } + this.inFlight = true; + const startedAt = performance.now(); + const params = new HttpParams().set('_', String(Date.now())); + + this.http + .get('/api/health', { params }) + .pipe( + timeout(4000), + catchError(() => of(null)) + ) + .subscribe((response) => { + const checkedAt = response?.timestamp || new Date().toISOString(); + if (response?.status === 'ok') { + this.snapshot.set({ + state: 'online', + latencyMs: Math.max(1, Math.round(performance.now() - startedAt)), + checkedAt + }); + } else { + this.snapshot.set({ + state: 'offline', + latencyMs: null, + checkedAt + }); + } + this.inFlight = false; + }); + } +} diff --git a/frontend/src/app/core/services/api.service.ts b/frontend/src/app/core/services/api.service.ts new file mode 100644 index 0000000..a140054 --- /dev/null +++ b/frontend/src/app/core/services/api.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +@Injectable({ providedIn: 'root' }) +export class ApiService { + readonly baseUrl = '/api'; + + constructor(public http: HttpClient) {} +} diff --git a/frontend/src/app/core/services/auth.service.ts b/frontend/src/app/core/services/auth.service.ts new file mode 100644 index 0000000..04ca6a9 --- /dev/null +++ b/frontend/src/app/core/services/auth.service.ts @@ -0,0 +1,88 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable, inject, signal } from '@angular/core'; +import { tap } from 'rxjs'; + +import { AppFont, FontService } from './font.service'; +import { AppLanguage, LanguageService } from './language.service'; + +export interface AuthUser { + id: number; + username: string; + preferred_language: AppLanguage; + preferred_font: AppFont; +} + +@Injectable({ providedIn: 'root' }) +export class AuthService { + private readonly api = '/api'; + private readonly tokenKey = 'routeros_token'; + private readonly language = inject(LanguageService); + private readonly font = inject(FontService); + + readonly user = signal(null); + + constructor(private http: HttpClient) {} + + login(username: string, password: string) { + const body = new URLSearchParams({ username, password }); + return this.http + .post<{ access_token: string; user: AuthUser }>(`${this.api}/auth/login`, body.toString(), { + headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }) + }) + .pipe( + tap((response) => { + localStorage.setItem(this.tokenKey, response.access_token); + this.setUserAndApplyPreferences(response.user); + }) + ); + } + + register(username: string, password: string) { + return this.http.post<{ id: number; username: string }>(`${this.api}/auth/register`, { username, password }); + } + + changePassword(current_password: string, new_password: string) { + return this.http.post<{ message: string }>(`${this.api}/auth/change-password`, { current_password, new_password }); + } + + restoreSession() { + const token = this.token(); + if (!token) { + this.user.set(null); + this.language.resetToGuestPreference(); + return; + } + this.http.get(`${this.api}/auth/me`).subscribe({ + next: (user) => this.setUserAndApplyPreferences(user), + error: () => this.logout() + }); + } + + updatePreferences(preferences: { preferred_language: AppLanguage; preferred_font: AppFont }) { + return this.http.put(`${this.api}/auth/preferences`, preferences).pipe( + tap((user) => { + this.setUserAndApplyPreferences(user); + }) + ); + } + + logout() { + localStorage.removeItem(this.tokenKey); + this.user.set(null); + this.language.resetToGuestPreference(); + } + + token() { + return localStorage.getItem(this.tokenKey); + } + + isLoggedIn() { + return !!this.token(); + } + + private setUserAndApplyPreferences(user: AuthUser) { + this.user.set(user); + this.language.applyForUser(user.preferred_language || 'pl'); + this.font.set(user.preferred_font || 'default'); + } +} diff --git a/frontend/src/app/core/services/font.service.ts b/frontend/src/app/core/services/font.service.ts new file mode 100644 index 0000000..f2727d6 --- /dev/null +++ b/frontend/src/app/core/services/font.service.ts @@ -0,0 +1,56 @@ +import { DOCUMENT } from '@angular/common'; +import { Injectable, computed, inject, signal } from '@angular/core'; + +export type AppFont = 'default' | 'adwaita_mono' | 'hack'; + +@Injectable({ providedIn: 'root' }) +export class FontService { + private readonly key = 'routeros_font'; + private readonly document = inject(DOCUMENT); + private readonly supportedFonts: AppFont[] = ['default', 'adwaita_mono', 'hack']; + private readonly fontState = signal('default'); + + readonly current = computed(() => this.fontState()); + + init() { + const stored = localStorage.getItem(this.key) as AppFont | null; + const font = stored && this.supportedFonts.includes(stored) ? stored : 'default'; + this.set(font); + } + + set(font: AppFont) { + const nextFont = this.supportedFonts.includes(font) ? font : 'default'; + this.fontState.set(nextFont); + localStorage.setItem(this.key, nextFont); + this.applyFont(nextFont); + } + + private applyFont(font: AppFont) { + const root = this.document.documentElement; + const body = this.document.body; + const families = this.fontFamilies(font); + root.style.setProperty('--font-body', families.body); + root.style.setProperty('--font-title', families.title); + body.setAttribute('data-app-font', font); + } + + private fontFamilies(font: AppFont): { body: string; title: string } { + switch (font) { + case 'adwaita_mono': + return { + body: "'Adwaita Mono', 'Roboto Mono', 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace", + title: "'Adwaita Mono', 'Roboto Mono', 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace" + }; + case 'hack': + return { + body: "Hack, 'Roboto Mono', 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace", + title: "Hack, 'Roboto Mono', 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace" + }; + default: + return { + body: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", + title: "'Roboto Mono', 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace" + }; + } + } +} diff --git a/frontend/src/app/core/services/language.service.ts b/frontend/src/app/core/services/language.service.ts new file mode 100644 index 0000000..9243c69 --- /dev/null +++ b/frontend/src/app/core/services/language.service.ts @@ -0,0 +1,69 @@ +import { Injectable, computed, inject, signal } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; + +export type AppLanguage = 'pl' | 'en' | 'es' | 'no'; + +export interface AppLanguageOption { + code: AppLanguage; + label: string; + flag: string; +} + +export const APP_LANGUAGE_OPTIONS: AppLanguageOption[] = [ + { code: 'pl', label: 'Polski', flag: '🇵🇱' }, + { code: 'en', label: 'English', flag: '🇬🇧' }, + { code: 'es', label: 'Español', flag: '🇪🇸' }, + { code: 'no', label: 'Norsk', flag: '🇳🇴' } +]; + +@Injectable({ providedIn: 'root' }) +export class LanguageService { + private readonly key = 'routeros_lang'; + private readonly translate = inject(TranslateService); + private readonly supportedLanguages = APP_LANGUAGE_OPTIONS.map((option) => option.code); + private readonly langState = signal('pl'); + + readonly current = computed(() => this.langState()); + readonly options = APP_LANGUAGE_OPTIONS; + + init() { + this.translate.setDefaultLang('pl'); + this.apply(this.guestPreference()); + } + + set(lang: AppLanguage) { + const nextLang = this.read(lang) || 'pl'; + localStorage.setItem(this.key, nextLang); + this.apply(nextLang); + } + + setForAuthenticatedUser(lang: AppLanguage) { + const nextLang = this.read(lang) || 'pl'; + localStorage.setItem(this.key, nextLang); + this.apply(nextLang); + } + + applyForUser(preferredLanguage?: AppLanguage | null) { + this.apply(this.read(preferredLanguage || null) || 'pl'); + } + + resetToGuestPreference() { + this.apply(this.guestPreference()); + } + + private guestPreference(): AppLanguage { + return this.read(localStorage.getItem(this.key)) || 'pl'; + } + + private apply(lang: AppLanguage) { + this.langState.set(lang); + this.translate.use(lang); + } + + private read(value: string | null | undefined): AppLanguage | null { + if (!value) { + return null; + } + return this.supportedLanguages.includes(value as AppLanguage) ? (value as AppLanguage) : null; + } +} diff --git a/frontend/src/app/core/services/layout.service.ts b/frontend/src/app/core/services/layout.service.ts new file mode 100644 index 0000000..a1617da --- /dev/null +++ b/frontend/src/app/core/services/layout.service.ts @@ -0,0 +1,22 @@ +import { Injectable, computed, signal } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class LayoutService { + private readonly collapsedState = signal(false); + private readonly mobileOpenState = signal(false); + + readonly collapsed = computed(() => this.collapsedState()); + readonly mobileOpen = computed(() => this.mobileOpenState()); + + toggleSidebar() { + if (typeof window !== 'undefined' && window.innerWidth < 992) { + this.mobileOpenState.update((value) => !value); + return; + } + this.collapsedState.update((value) => !value); + } + + closeMobileSidebar() { + this.mobileOpenState.set(false); + } +} diff --git a/frontend/src/app/core/services/theme.service.ts b/frontend/src/app/core/services/theme.service.ts new file mode 100644 index 0000000..f8bd27b --- /dev/null +++ b/frontend/src/app/core/services/theme.service.ts @@ -0,0 +1,41 @@ +import { Injectable, computed, signal } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class ThemeService { + private readonly key = 'routeros_theme'; + private readonly modeState = signal<'light' | 'dark'>('dark'); + + readonly mode = computed(() => this.modeState()); + readonly isDark = computed(() => this.modeState() === 'dark'); + + init() { + const mode = (localStorage.getItem(this.key) as 'light' | 'dark' | null) || 'dark'; + this.set(mode); + } + + toggle() { + this.set(this.modeState() === 'dark' ? 'light' : 'dark'); + } + + set(mode: 'light' | 'dark') { + this.modeState.set(mode); + + const isDark = mode === 'dark'; + const html = document.documentElement; + const body = document.body; + + html.classList.toggle('dark-theme', isDark); + body.classList.toggle('dark-theme', isDark); + + html.setAttribute('data-theme', mode); + body.setAttribute('data-theme', mode); + html.style.colorScheme = isDark ? 'dark' : 'light'; + body.style.colorScheme = isDark ? 'dark' : 'light'; + + localStorage.setItem(this.key, mode); + + requestAnimationFrame(() => { + window.dispatchEvent(new Event('resize')); + }); + } +} \ No newline at end of file diff --git a/frontend/src/app/core/services/ui.service.ts b/frontend/src/app/core/services/ui.service.ts new file mode 100644 index 0000000..6582692 --- /dev/null +++ b/frontend/src/app/core/services/ui.service.ts @@ -0,0 +1,81 @@ +import { Injectable, inject } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ConfirmationService, MessageService } from 'primeng/api'; + +interface ConfirmOptions { + messageKey: string; + params?: Record; + headerKey?: string; + acceptKey?: string; + rejectKey?: string; + acceptButtonStyleClass?: string; +} + +@Injectable({ providedIn: 'root' }) +export class UiService { + private readonly messageService = inject(MessageService); + private readonly confirmationService = inject(ConfirmationService); + private readonly translate = inject(TranslateService); + + success(detailKey: string, params?: Record) { + this.messageService.add({ + severity: 'success', + summary: this.t('toast.success'), + detail: this.t(detailKey, params) + }); + } + + info(detailKey: string, params?: Record) { + this.messageService.add({ + severity: 'info', + summary: this.t('toast.info'), + detail: this.t(detailKey, params) + }); + } + + error(detailKey: string, params?: Record) { + this.messageService.add({ + severity: 'error', + summary: this.t('toast.error'), + detail: this.t(detailKey, params) + }); + } + + clear() { + this.messageService.clear(); + } + + confirm(options: ConfirmOptions): Promise { + return new Promise((resolve) => { + let resolved = false; + const finish = (value: boolean) => { + if (!resolved) { + resolved = true; + resolve(value); + } + }; + + this.confirmationService.confirm({ + header: this.t(options.headerKey ?? 'confirm.header'), + message: this.t(options.messageKey, options.params), + icon: 'pi pi-exclamation-triangle', + acceptLabel: this.t(options.acceptKey ?? 'common.confirm'), + rejectLabel: this.t(options.rejectKey ?? 'common.cancel'), + acceptButtonStyleClass: options.acceptButtonStyleClass ?? 'p-button-danger', + rejectButtonStyleClass: 'p-button-text', + closeOnEscape: true, + dismissableMask: true, + accept: () => finish(true), + reject: () => finish(false) + }); + }); + } + + instant(key: string, params?: Record) { + return this.t(key, params); + } + + private t(key: string, params?: Record): string { + return this.translate.instant(key, params); + } +} diff --git a/frontend/src/app/core/theme-preset.ts b/frontend/src/app/core/theme-preset.ts new file mode 100644 index 0000000..a33d42b --- /dev/null +++ b/frontend/src/app/core/theme-preset.ts @@ -0,0 +1,134 @@ +import { definePreset } from '@primeuix/themes'; +import Lara from '@primeuix/themes/lara'; + +const AppPreset = definePreset(Lara, { + primitive: { + borderRadius: { + none: '0', + xs: '8px', + sm: '10px', + md: '12px', + lg: '16px', + xl: '20px' + } + }, + semantic: { + primary: { + 50: '#f6eee8', + 100: '#ecd7c8', + 200: '#dfb79e', + 300: '#cf9571', + 400: '#b9754d', + 500: '#8d593a', + 600: '#794a30', + 700: '#653d28', + 800: '#533220', + 900: '#43291a', + 950: '#2a1910' + }, + colorScheme: { + light: { + surface: { + 0: '#ffffff', + 50: '#f8f8f5', + 100: '#f1f1ed', + 200: '#e6e6e0', + 300: '#dfdfd8', + 400: '#d0d0c8', + 500: '#b7b7ae', + 600: '#8f8f86', + 700: '#6e6e67', + 800: '#4f4f49', + 900: '#31312d', + 950: '#1e1e1b' + }, + content: { + background: '#f8f8f5', + hoverBackground: '#f1f1ed', + borderColor: 'rgba(17, 20, 23, 0.12)', + color: '#111417', + hoverColor: '#111417' + }, + formField: { + background: 'rgba(255, 255, 255, 0.5)', + disabledBackground: '#f1f1ed', + borderColor: 'rgba(17, 20, 23, 0.2)', + hoverBorderColor: '#8d593a', + focusBorderColor: '#8d593a', + color: '#111417', + placeholderColor: '#5e666e', + floatLabelColor: '#5e666e' + }, + overlay: { + select: { + background: '#f8f8f5', + borderColor: 'rgba(17, 20, 23, 0.12)', + color: '#111417' + }, + popover: { + background: '#f8f8f5', + borderColor: 'rgba(17, 20, 23, 0.12)', + color: '#111417' + }, + modal: { + background: '#f8f8f5', + borderColor: 'rgba(17, 20, 23, 0.12)', + color: '#111417' + } + } + }, + dark: { + surface: { + 0: '#17212b', + 50: '#1d2733', + 100: '#222d3a', + 200: '#2d3947', + 300: '#3a4858', + 400: '#516173', + 500: '#6c7c8d', + 600: '#93a5b6', + 700: '#b7c7d6', + 800: '#dae4ec', + 900: '#edf2f7', + 950: '#f7fbff' + }, + content: { + background: '#1d2733', + hoverBackground: '#222d3a', + borderColor: 'rgba(146, 170, 194, 0.16)', + color: '#dae4ec', + hoverColor: '#dae4ec' + }, + formField: { + background: 'rgba(255, 255, 255, 0.03)', + disabledBackground: '#222d3a', + borderColor: 'rgba(146, 170, 194, 0.25)', + hoverBorderColor: '#4b90d9', + focusBorderColor: '#4b90d9', + color: '#dae4ec', + placeholderColor: '#93a5b6', + floatLabelColor: '#93a5b6' + }, + overlay: { + select: { + background: '#1d2733', + borderColor: 'rgba(146, 170, 194, 0.16)', + color: '#dae4ec' + }, + popover: { + background: '#1d2733', + borderColor: 'rgba(146, 170, 194, 0.16)', + color: '#dae4ec' + }, + modal: { + background: '#1d2733', + borderColor: 'rgba(146, 170, 194, 0.16)', + color: '#dae4ec' + } + } + } + } + } +}); + +export default AppPreset; diff --git a/frontend/src/app/features/auth/change-password-page.component.html b/frontend/src/app/features/auth/change-password-page.component.html new file mode 100644 index 0000000..cd32840 --- /dev/null +++ b/frontend/src/app/features/auth/change-password-page.component.html @@ -0,0 +1,50 @@ + + +
+ +
+
+ {{ 'auth.passwordStrength' | translate }} + {{ passwordStrengthLabel }} +
+
+
+
+ + {{ 'auth.ruleLength' | translate }} +
+
+ + {{ 'auth.ruleDigit' | translate }} +
+
+ + {{ 'auth.ruleMatch' | translate }} +
+
+
+
+ + +
+ + + + + + + + + + + + + {{ passwordsMatch ? ('auth.passwordsMatchHint' | translate) : ('auth.passwordsMismatch' | translate) }} + {{ error }} + +
+
+
diff --git a/frontend/src/app/features/auth/change-password-page.component.ts b/frontend/src/app/features/auth/change-password-page.component.ts new file mode 100644 index 0000000..29b7de0 --- /dev/null +++ b/frontend/src/app/features/auth/change-password-page.component.ts @@ -0,0 +1,90 @@ +import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Router, RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { InputTextModule } from 'primeng/inputtext'; + +import { AuthService } from '../../core/services/auth.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; +import { SectionCardComponent } from '../../shared/ui/section-card.component'; + +@Component({ + standalone: true, + imports: [CommonModule, ReactiveFormsModule, RouterLink, TranslateModule, ButtonModule, InputTextModule, PageHeaderComponent, SectionCardComponent], + templateUrl: './change-password-page.component.html' +}) +export class ChangePasswordPageComponent { + private readonly fb = inject(FormBuilder); + private readonly auth = inject(AuthService); + private readonly router = inject(Router); + private readonly ui = inject(UiService); + + error = ''; + submitting = false; + readonly form = this.fb.nonNullable.group({ + current_password: ['', Validators.required], + new_password: ['', [Validators.required, Validators.minLength(4)]], + confirmPassword: ['', [Validators.required, Validators.minLength(4)]] + }); + + + get hasMinLength(): boolean { + return (this.form.controls.new_password.value || '').length >= 8; + } + + get hasDigit(): boolean { + return /[0-9]/.test(this.form.controls.new_password.value || ''); + } + + get passwordStrengthPercent(): number { + const value = this.form.controls.new_password.value || ''; + let score = 0; + if (value.length >= 8) score += 35; + if (/[A-Z]/.test(value)) score += 20; + if (/[a-z]/.test(value)) score += 15; + if (/[0-9]/.test(value)) score += 15; + if (/[^A-Za-z0-9]/.test(value)) score += 15; + return Math.min(100, score); + } + + get passwordStrengthLabel(): string { + const score = this.passwordStrengthPercent; + if (score >= 80) return this.ui.instant('auth.passwordStrong'); + if (score >= 50) return this.ui.instant('auth.passwordMedium'); + return this.ui.instant('auth.passwordWeak'); + } + + get passwordsMatch(): boolean { + const { new_password, confirmPassword } = this.form.getRawValue(); + return !!new_password && new_password === confirmPassword; + } + + submit() { + if (this.form.invalid || this.submitting) { + return; + } + this.error = ''; + const { current_password, new_password, confirmPassword } = this.form.getRawValue(); + if (new_password !== confirmPassword) { + this.error = this.ui.instant('auth.passwordsMismatch'); + return; + } + this.submitting = true; + this.auth.changePassword(current_password, new_password).subscribe({ + next: () => { + this.ui.success('toast.passwordChanged'); + setTimeout(() => this.router.navigate(['/']), 500); + }, + error: (err) => { + this.error = err?.error?.detail ?? this.ui.instant('auth.changePasswordFailed'); + this.submitting = false; + }, + complete: () => { + this.submitting = false; + } + }); + } +} diff --git a/frontend/src/app/features/auth/login-page.component.html b/frontend/src/app/features/auth/login-page.component.html new file mode 100644 index 0000000..92b894c --- /dev/null +++ b/frontend/src/app/features/auth/login-page.component.html @@ -0,0 +1,29 @@ +
+ + +
+
+

{{ 'auth.login' | translate }}

+

{{ 'auth.loginSubtitle' | translate }}

+
+ +
+ + + + + + + + + + + {{ error }} + + +
+
+
diff --git a/frontend/src/app/features/auth/login-page.component.ts b/frontend/src/app/features/auth/login-page.component.ts new file mode 100644 index 0000000..285924b --- /dev/null +++ b/frontend/src/app/features/auth/login-page.component.ts @@ -0,0 +1,49 @@ +import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Router, RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { InputTextModule } from 'primeng/inputtext'; + +import { AuthService } from '../../core/services/auth.service'; +import { UiService } from '../../core/services/ui.service'; +import { AuthToolbarComponent } from '../../shared/auth/auth-toolbar.component'; + +@Component({ + standalone: true, + imports: [CommonModule, ReactiveFormsModule, RouterLink, TranslateModule, ButtonModule, InputTextModule, AuthToolbarComponent], + templateUrl: './login-page.component.html' +}) +export class LoginPageComponent { + private readonly fb = inject(FormBuilder); + private readonly auth = inject(AuthService); + private readonly router = inject(Router); + private readonly ui = inject(UiService); + + error = ''; + submitting = false; + readonly form = this.fb.nonNullable.group({ + username: ['', Validators.required], + password: ['', Validators.required] + }); + + submit() { + if (this.form.invalid || this.submitting) { + return; + } + this.error = ''; + this.submitting = true; + const { username, password } = this.form.getRawValue(); + this.auth.login(username, password).subscribe({ + next: () => this.router.navigate(['/']), + error: (err) => { + this.error = err?.error?.detail ?? this.ui.instant('auth.loginFailed'); + this.submitting = false; + }, + complete: () => { + this.submitting = false; + } + }); + } +} diff --git a/frontend/src/app/features/auth/register-page.component.html b/frontend/src/app/features/auth/register-page.component.html new file mode 100644 index 0000000..b43502e --- /dev/null +++ b/frontend/src/app/features/auth/register-page.component.html @@ -0,0 +1,28 @@ +
+ +
+
+

{{ 'auth.register' | translate }}

+
+
+ + + + + + + + + + + + + {{ error }} + {{ success }} + +
+
+
diff --git a/frontend/src/app/features/auth/register-page.component.ts b/frontend/src/app/features/auth/register-page.component.ts new file mode 100644 index 0000000..cbe484b --- /dev/null +++ b/frontend/src/app/features/auth/register-page.component.ts @@ -0,0 +1,59 @@ +import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Router, RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { InputTextModule } from 'primeng/inputtext'; + +import { AuthService } from '../../core/services/auth.service'; +import { UiService } from '../../core/services/ui.service'; +import { AuthToolbarComponent } from '../../shared/auth/auth-toolbar.component'; + +@Component({ + standalone: true, + imports: [CommonModule, ReactiveFormsModule, RouterLink, TranslateModule, ButtonModule, InputTextModule, AuthToolbarComponent], + templateUrl: './register-page.component.html' +}) +export class RegisterPageComponent { + private readonly fb = inject(FormBuilder); + private readonly auth = inject(AuthService); + private readonly router = inject(Router); + private readonly ui = inject(UiService); + + error = ''; + success = ''; + submitting = false; + readonly form = this.fb.nonNullable.group({ + username: ['', [Validators.required, Validators.minLength(3)]], + password: ['', [Validators.required, Validators.minLength(4)]], + confirmPassword: ['', [Validators.required, Validators.minLength(4)]] + }); + + submit() { + if (this.form.invalid || this.submitting) { + return; + } + this.error = ''; + this.success = ''; + const { username, password, confirmPassword } = this.form.getRawValue(); + if (password !== confirmPassword) { + this.error = this.ui.instant('auth.passwordsMismatch'); + return; + } + this.submitting = true; + this.auth.register(username, password).subscribe({ + next: () => { + this.success = this.ui.instant('auth.accountCreated'); + setTimeout(() => this.router.navigate(['/login']), 500); + }, + error: (err) => { + this.error = err?.error?.detail ?? this.ui.instant('auth.registrationFailed'); + this.submitting = false; + }, + complete: () => { + this.submitting = false; + } + }); + } +} diff --git a/frontend/src/app/features/dashboard/dashboard-page.component.html b/frontend/src/app/features/dashboard/dashboard-page.component.html new file mode 100644 index 0000000..183f1ed --- /dev/null +++ b/frontend/src/app/features/dashboard/dashboard-page.component.html @@ -0,0 +1,107 @@ + + +
+ + + + +
+ + +
+
+ + +
+ +
+
+ {{ 'dashboard.latestSnapshot' | translate }} + {{ latestBackupLabel }} + {{ latestBackupHint }} +
+
+ {{ 'dashboard.coverageLabel' | translate }} + {{ coveragePercent }}% + {{ 'dashboard.coverageHint' | translate }} +
+
+ {{ 'dashboard.weeklyActivityLabel' | translate }} + {{ backupsLast7Days }} + {{ 'dashboard.weeklyActivityHint' | translate }} +
+
+ {{ 'dashboard.busiestRouterLabel' | translate }} + {{ busiestRouterLabel }} + {{ busiestRouterHint }} +
+
+
+
+ +
+ +
+
+
+ {{ formatPercent(usedPercent) }} + {{ 'dashboard.diskUsage' | translate }} +
+
+ +
+

{{ 'dashboard.storageSubtitle' | translate }}

+
+
+ {{ 'dashboard.totalDisk' | translate }} + {{ formatBytes(data.storage.total) }} +
+
+ {{ 'dashboard.usedSpace' | translate }} + {{ formatBytes(usedBytes) }} +
+
+ {{ 'dashboard.freeSpace' | translate }} + {{ formatBytes(data.storage.free) }} +
+
+ {{ 'dashboard.folderUsage' | translate }} + {{ formatBytes(data.storage.folder_used) }} +
+
+
+
+
+ + + +
+
+

{{ 'dashboard.weeklyActivityHint' | translate }}

+ {{ backupsLast7Days }} +
+ +
+
+ {{ item.label }} +
+ +
+ {{ item.value }} +
+
+
+
+
+
+ + +
+ +

{{ 'dashboard.noActivity' | translate }}

+
+
diff --git a/frontend/src/app/features/dashboard/dashboard-page.component.ts b/frontend/src/app/features/dashboard/dashboard-page.component.ts new file mode 100644 index 0000000..ef6d6c3 --- /dev/null +++ b/frontend/src/app/features/dashboard/dashboard-page.component.ts @@ -0,0 +1,413 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnInit, inject } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { forkJoin } from 'rxjs'; + +import { ApiService } from '../../core/services/api.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; +import { SectionCardComponent } from '../../shared/ui/section-card.component'; +import { StatCardComponent } from '../../shared/ui/stat-card.component'; + +interface DashboardData { + routers_count: number; + export_count: number; + binary_count: number; + total_backups: number; + recent_logs: { timestamp: string; message: string }[]; + storage: { total: number; used: number; free: number; folder_used: number; usage_percent: number }; +} + +interface BackupInventoryItem { + id: number; + router_id: number; + router_name?: string; + backup_type: 'export' | 'binary'; + created_at: string; + file_size?: number | null; +} + +interface RouterInventoryItem { + id: number; + name: string; +} + +type ChartTone = 'accent' | 'success' | 'info' | 'warning'; + +interface StorageChartRow { + labelKey: string; + value: string; + percent: number; + tone: ChartTone; +} + +interface StorageActivityBar { + label: string; + fullLabel: string; + value: number; + height: number; +} + +interface RouterBackupBar { + label: string; + value: number; + percent: number; +} + +@Component({ + standalone: true, + imports: [CommonModule, TranslateModule, ButtonModule, PageHeaderComponent, SectionCardComponent, StatCardComponent], + templateUrl: './dashboard-page.component.html' +}) +export class DashboardPageComponent implements OnInit { + private readonly api = inject(ApiService); + private readonly ui = inject(UiService); + + data?: DashboardData; + backups: BackupInventoryItem[] = []; + routers: RouterInventoryItem[] = []; + exporting = false; + runningBinary = false; + readonly activityPageSize = 6; + activityPage = 0; + + ngOnInit() { + this.load(); + } + + load() { + forkJoin({ + dashboard: this.api.http.get(`${this.api.baseUrl}/dashboard`), + backups: this.api.http.get(`${this.api.baseUrl}/backups`), + routers: this.api.http.get(`${this.api.baseUrl}/routers`) + }).subscribe(({ dashboard, backups, routers }) => { + this.data = dashboard; + this.backups = backups; + this.routers = routers; + this.activityPage = 0; + }); + } + + exportAll() { + if (this.exporting) { + return; + } + this.exporting = true; + this.api.http.post(`${this.api.baseUrl}/backups/routers/export-all`, {}).subscribe({ + next: (result) => { + this.ui.success('toast.exportedRouters', { count: result.filter((item) => item.status === 'ok').length }); + this.load(); + }, + complete: () => { + this.exporting = false; + } + }); + } + + binaryAll() { + if (this.runningBinary) { + return; + } + this.runningBinary = true; + this.api.http.post(`${this.api.baseUrl}/backups/routers/binary-all`, {}).subscribe({ + next: (result) => { + this.ui.success('toast.binaryCompletedRouters', { count: result.filter((item) => item.status === 'ok').length }); + this.load(); + }, + complete: () => { + this.runningBinary = false; + } + }); + } + + get usedBytes(): number { + const storage = this.data?.storage; + if (!storage) { + return 0; + } + const computed = Math.max(0, Number(storage.total || 0) - Number(storage.free || 0)); + if (computed > 0) { + return computed; + } + return Math.max(0, Number(storage.used || 0)); + } + + get usedPercent(): number { + const total = Number(this.data?.storage.total || 0); + return total > 0 ? (this.usedBytes / total) * 100 : 0; + } + + get freePercent(): number { + const storage = this.data?.storage; + const total = Number(storage?.total || 0); + const free = Math.max(0, Number(storage?.free || 0)); + return total > 0 ? (free / total) * 100 : 0; + } + + get folderPercent(): number { + const storage = this.data?.storage; + const total = Number(storage?.total || 0); + const folderUsed = Math.max(0, Number(storage?.folder_used || 0)); + return total > 0 ? (folderUsed / total) * 100 : 0; + } + + get storageCapacityRows(): StorageChartRow[] { + const storage = this.data?.storage; + if (!storage) { + return []; + } + return [ + { labelKey: 'dashboard.totalDisk', value: this.formatBytes(storage.total), percent: 100, tone: 'accent' }, + { labelKey: 'dashboard.usedSpace', value: this.formatBytes(this.usedBytes), percent: this.usedPercent, tone: 'warning' }, + { labelKey: 'dashboard.freeSpace', value: this.formatBytes(storage.free), percent: this.freePercent, tone: 'success' }, + { labelKey: 'dashboard.folderUsage', value: this.formatBytes(storage.folder_used), percent: this.folderPercent, tone: 'info' } + ]; + } + + get backupMixRows(): StorageChartRow[] { + const exportCount = Number(this.data?.export_count || 0); + const binaryCount = Number(this.data?.binary_count || 0); + const total = exportCount + binaryCount; + return [ + { + labelKey: 'dashboard.exportsCard', + value: String(exportCount), + percent: total > 0 ? (exportCount / total) * 100 : 0, + tone: 'accent' + }, + { + labelKey: 'dashboard.binaryCard', + value: String(binaryCount), + percent: total > 0 ? (binaryCount / total) * 100 : 0, + tone: 'warning' + } + ]; + } + + get backupActivityRows(): StorageActivityBar[] { + if (!this.backups.length) { + return []; + } + + const today = new Date(); + today.setHours(0, 0, 0, 0); + const counts = new Map(); + + for (const backup of this.backups) { + const value = new Date(backup.created_at); + value.setHours(0, 0, 0, 0); + const key = value.toISOString().slice(0, 10); + counts.set(key, (counts.get(key) || 0) + 1); + } + + const items: StorageActivityBar[] = []; + for (let offset = 6; offset >= 0; offset -= 1) { + const date = new Date(today); + date.setDate(today.getDate() - offset); + const key = date.toISOString().slice(0, 10); + const label = `${String(date.getDate()).padStart(2, '0')}.${String(date.getMonth() + 1).padStart(2, '0')}`; + items.push({ label, fullLabel: key, value: counts.get(key) || 0, height: 0 }); + } + + const maxValue = Math.max(...items.map((item) => item.value), 0); + return items.map((item) => ({ + ...item, + height: item.value > 0 && maxValue > 0 ? Math.max(16, Math.round((item.value / maxValue) * 100)) : 0 + })); + } + + get routerBackupRows(): RouterBackupBar[] { + if (!this.backups.length) { + return []; + } + + const counters = new Map(); + for (const backup of this.backups) { + const entry = counters.get(backup.router_id) || { label: backup.router_name || `#${backup.router_id}`, value: 0, percent: 0 }; + entry.value += 1; + counters.set(backup.router_id, entry); + } + + const sorted = Array.from(counters.values()).sort((a, b) => b.value - a.value).slice(0, 5); + const maxValue = Math.max(...sorted.map((item) => item.value), 0); + return sorted.map((item) => ({ ...item, percent: maxValue > 0 ? (item.value / maxValue) * 100 : 0 })); + } + + get averageBackupsPerRouter(): string { + if (!this.data?.routers_count) { + return '0'; + } + return (this.data.total_backups / this.data.routers_count).toFixed(1); + } + + get coveragePercent(): number { + if (!this.routers.length) { + return 0; + } + const routersWithBackups = new Set(this.backups.map((item) => item.router_id)).size; + return Math.round((routersWithBackups / this.routers.length) * 100); + } + + get exportsSharePercent(): number { + if (!this.backups.length) { + return 0; + } + return Math.round((this.backups.filter((item) => item.backup_type === 'export').length / this.backups.length) * 100); + } + + get backupsLast7Days(): number { + const threshold = Date.now() - 7 * 24 * 60 * 60 * 1000; + return this.backups.filter((item) => new Date(item.created_at).getTime() >= threshold).length; + } + + get latestBackupLabel(): string { + const latest = this.latestBackup; + if (!latest) { + return this.ui.instant('dashboard.noneLabel'); + } + return latest.router_name || `#${latest.router_id}`; + } + + get latestBackupHint(): string { + const latest = this.latestBackup; + if (!latest) { + return this.ui.instant('dashboard.noActivity'); + } + return `${latest.backup_type === 'export' ? this.ui.instant('files.exportType') : this.ui.instant('files.binaryType')} · ${this.relativeAge(latest.created_at)}`; + } + + get busiestRouterLabel(): string { + const busiest = this.busiestRouter; + if (!busiest) { + return this.ui.instant('dashboard.noneLabel'); + } + return busiest.name; + } + + get busiestRouterHint(): string { + const busiest = this.busiestRouter; + if (!busiest) { + return this.ui.instant('dashboard.noActivity'); + } + return this.ui.instant('dashboard.routerSnapshotsHint', { count: busiest.count }); + } + + get activityToday(): number { + if (!this.data?.recent_logs?.length) { + return 0; + } + const today = new Date(); + return this.data.recent_logs.filter((log) => { + const value = new Date(log.timestamp); + return value.getFullYear() === today.getFullYear() && value.getMonth() === today.getMonth() && value.getDate() === today.getDate(); + }).length; + } + + get activityPageCount(): number { + const total = this.data?.recent_logs?.length || 0; + return Math.max(1, Math.ceil(total / this.activityPageSize)); + } + + get pagedRecentLogs() { + if (!this.data?.recent_logs?.length) { + return []; + } + const start = this.activityPage * this.activityPageSize; + return this.data.recent_logs.slice(start, start + this.activityPageSize); + } + + get activityRangeLabel(): string { + const total = this.data?.recent_logs?.length || 0; + if (!total) { + return '0 / 0'; + } + const start = this.activityPage * this.activityPageSize + 1; + const end = Math.min(total, start + this.activityPageSize - 1); + return `${start}-${end} / ${total}`; + } + + previousActivityPage() { + this.activityPage = Math.max(0, this.activityPage - 1); + } + + nextActivityPage() { + this.activityPage = Math.min(this.activityPageCount - 1, this.activityPage + 1); + } + + get storageRingBackground(): string { + const safePercent = Math.min(100, Math.max(0, this.usedPercent)); + return `conic-gradient(var(--accent) 0deg ${safePercent * 3.6}deg, rgba(129, 149, 167, 0.18) ${safePercent * 3.6}deg 360deg)`; + } + + formatBytes(value: number): string { + if (!value) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let size = value; + let unit = 0; + while (size >= 1024 && unit < units.length - 1) { + size /= 1024; + unit += 1; + } + return `${size.toFixed(size >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`; + } + + formatPercent(value: number): string { + return `${Number(value || 0).toFixed(1)}%`; + } + + relativeAge(value: string): string { + const diff = Date.now() - new Date(value).getTime(); + const hours = Math.floor(diff / 3_600_000); + if (hours < 1) { + const minutes = Math.max(1, Math.floor(diff / 60_000)); + return this.ui.instant('files.minutesAgo', { value: minutes }); + } + if (hours < 24) { + return this.ui.instant('files.hoursAgo', { value: hours }); + } + const days = Math.floor(hours / 24); + return this.ui.instant('files.daysAgo', { value: days }); + } + + activityTone(message: string): 'success' | 'danger' | 'warning' | 'info' { + const normalized = message.toLowerCase(); + if (normalized.includes('fail') || normalized.includes('error')) return 'danger'; + if (normalized.includes('cleanup') || normalized.includes('retention')) return 'warning'; + if (normalized.includes('upload') || normalized.includes('email')) return 'info'; + return 'success'; + } + + activityIcon(message: string): string { + const tone = this.activityTone(message); + if (tone === 'danger') return 'pi pi-exclamation-triangle'; + if (tone === 'warning') return 'pi pi-broom'; + if (tone === 'info') return 'pi pi-send'; + return 'pi pi-check'; + } + + activityLabel(message: string): string { + const tone = this.activityTone(message); + if (tone === 'danger') return this.ui.instant('dashboard.activityFailure'); + if (tone === 'warning') return this.ui.instant('dashboard.activityMaintenance'); + if (tone === 'info') return this.ui.instant('dashboard.activityDelivery'); + return this.ui.instant('dashboard.activitySuccess'); + } + + private get latestBackup(): BackupInventoryItem | undefined { + return this.backups.slice().sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0]; + } + + private get busiestRouter(): { name: string; count: number } | null { + if (!this.backups.length) { + return null; + } + const counters = new Map(); + for (const backup of this.backups) { + const entry = counters.get(backup.router_id) || { name: backup.router_name || `#${backup.router_id}`, count: 0 }; + entry.count += 1; + counters.set(backup.router_id, entry); + } + return Array.from(counters.values()).sort((a, b) => b.count - a.count)[0] || null; + } +} diff --git a/frontend/src/app/features/diff-configs/diff-configs-page.component.html b/frontend/src/app/features/diff-configs/diff-configs-page.component.html new file mode 100644 index 0000000..0c63037 --- /dev/null +++ b/frontend/src/app/features/diff-configs/diff-configs-page.component.html @@ -0,0 +1,143 @@ + + +
+ + + + +
+ + +
+
+ + + + +
+ + + +
+
+ +
+
+
+ {{ 'files.compareOlder' | translate }} + +
+ +
+ {{ item.file_name }} + {{ item.router_name || item.router_id }} · {{ relativeAge(item.created_at) }} +
+ +
+
+
+ + + +
+
+ {{ 'files.compareNewer' | translate }} + +
+ +
+ {{ item.file_name }} + {{ item.router_name || item.router_id }} · {{ relativeAge(item.created_at) }} +
+ +
+
+
+
+
+
+ + + + + + {{ 'files.fileColumn' | translate }} + {{ 'files.routerColumn' | translate }} + {{ 'files.createdColumn' | translate }} + {{ 'files.compareColumn' | translate }} + + + + + +
{{ item.file_name }}
+ {{ 'files.checksum' | translate }}: {{ checksumShort(item.checksum) }} + + +
{{ item.router_name || item.router_id }}
+ ID {{ item.router_id }} + + +
{{ item.created_at | date: 'dd.MM.yyyy HH:mm' }}
+ {{ relativeAge(item.created_at) }} + + +
+ + + +
+ + +
+
+
+ + +
{{ viewedExport }}
+
+ + +
+
+
+
{{ diff.left_file_name }}
+ {{ 'files.compareOlder' | translate }} +
+
+
+
{{ diff.right_file_name }}
+ {{ 'files.compareNewer' | translate }} +
+
+ +{{ diff.stats.added }} + -{{ diff.stats.removed }} + ~{{ diff.stats.modified }} +
+
+ +
+
+ +
+
+
+ {{ line.left_number || '' }} +
{{ line.left_text || ' ' }}
+
+
+ {{ line.right_number || '' }} +
{{ line.right_text || ' ' }}
+
+
+
+ + +
{{ diff.diff_text }}
+
+
+
diff --git a/frontend/src/app/features/diff-configs/diff-configs-page.component.ts b/frontend/src/app/features/diff-configs/diff-configs-page.component.ts new file mode 100644 index 0000000..a71ec78 --- /dev/null +++ b/frontend/src/app/features/diff-configs/diff-configs-page.component.ts @@ -0,0 +1,248 @@ +import { CommonModule } from '@angular/common'; +import { HttpParams } from '@angular/common/http'; +import { Component, OnInit, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { SelectModule } from 'primeng/select'; +import { TableModule } from 'primeng/table'; +import { TagModule } from 'primeng/tag'; + +import { ApiService } from '../../core/services/api.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; +import { SectionCardComponent } from '../../shared/ui/section-card.component'; +import { StatCardComponent } from '../../shared/ui/stat-card.component'; + +interface BackupFile { + id: number; + router_id: number; + router_name?: string; + file_name: string; + backup_type: 'export' | 'binary'; + created_at: string; + checksum?: string | null; + file_size?: number | null; +} + +interface BackupDiffLine { + type: 'context' | 'added' | 'removed' | 'modified'; + left_number?: number | null; + right_number?: number | null; + left_text: string; + right_text: string; +} + +interface BackupDiffStats { + added: number; + removed: number; + modified: number; + context: number; +} + +interface BackupDiffResponse { + left_backup_id: number; + right_backup_id: number; + left_file_name?: string | null; + right_file_name?: string | null; + diff_text: string; + lines: BackupDiffLine[]; + stats?: BackupDiffStats | null; +} + +@Component({ + standalone: true, + imports: [CommonModule, FormsModule, TranslateModule, ButtonModule, DialogModule, SelectModule, TableModule, TagModule, PageHeaderComponent, SectionCardComponent, StatCardComponent], + templateUrl: './diff-configs-page.component.html' +}) +export class DiffConfigsPageComponent implements OnInit { + private readonly api = inject(ApiService); + private readonly ui = inject(UiService); + + files: BackupFile[] = []; + routers: { id: number; name: string }[] = []; + routerId: number | null = null; + compareLeftId: number | null = null; + compareRightId: number | null = null; + previewVisible = false; + diffVisible = false; + compareBusy = false; + loading = false; + previewTitle = ''; + viewedExport = ''; + diffData: BackupDiffResponse | null = null; + lastCompared: { left: number; right: number } | null = null; + + get routerOptions() { + return [{ label: this.ui.instant('files.allRouters'), value: null }, ...this.routers.map((item) => ({ label: item.name, value: item.id }))]; + } + + get exportFiles(): BackupFile[] { + return this.files + .filter((item) => item.backup_type === 'export' && (this.routerId === null || item.router_id === this.routerId)) + .slice() + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + } + + get compareOptions() { + return this.exportFiles.map((item) => ({ + label: `${item.router_name || item.router_id} · ${item.file_name}`, + value: item.id + })); + } + + get compareLeft(): BackupFile | undefined { + return this.files.find((item) => item.id === this.compareLeftId); + } + + get compareRight(): BackupFile | undefined { + return this.files.find((item) => item.id === this.compareRightId); + } + + get compareReady(): boolean { + return !!this.compareLeftId && !!this.compareRightId && this.compareLeftId !== this.compareRightId; + } + + get availableExportsCount(): number { + return this.exportFiles.length; + } + + get selectedRouterLabel(): string { + if (this.routerId === null) { + return this.ui.instant('files.allRouters'); + } + return this.routers.find((item) => item.id === this.routerId)?.name || `#${this.routerId}`; + } + + get lastDiffLabel(): string { + if (!this.diffData?.left_file_name || !this.diffData?.right_file_name) { + return this.ui.instant('diffConfigs.noneSelected'); + } + return `${this.diffData.left_file_name} → ${this.diffData.right_file_name}`; + } + + ngOnInit() { + this.api.http.get(`${this.api.baseUrl}/routers`).subscribe((routers) => { + this.routers = routers.map((item) => ({ id: item.id, name: item.name })); + }); + this.load(); + } + + load() { + this.loading = true; + let params = new HttpParams().set('backup_type', 'export').set('sort_by', 'created_at').set('order', 'desc'); + if (this.routerId !== null) { + params = params.set('router_id', String(this.routerId)); + } + this.api.http.get(`${this.api.baseUrl}/backups`, { params }).subscribe({ + next: (files) => { + this.files = files; + if (this.compareLeftId && !this.files.some((item) => item.id === this.compareLeftId)) { + this.compareLeftId = null; + } + if (this.compareRightId && !this.files.some((item) => item.id === this.compareRightId)) { + this.compareRightId = null; + } + }, + complete: () => { + this.loading = false; + } + }); + } + + assignCompare(side: 'left' | 'right', item: BackupFile) { + if (side === 'left') { + this.compareLeftId = item.id; + return; + } + this.compareRightId = item.id; + } + + fillLatestPair() { + if (this.exportFiles.length < 2) { + return; + } + const [right, left] = this.exportFiles; + this.compareLeftId = left.id; + this.compareRightId = right.id; + } + + swapCompare() { + const left = this.compareLeftId; + this.compareLeftId = this.compareRightId; + this.compareRightId = left; + } + + viewExport(item: BackupFile) { + this.api.http.get<{ content: string }>(`${this.api.baseUrl}/backups/${item.id}/view`).subscribe((response) => { + this.viewedExport = response.content; + this.previewTitle = item.file_name; + this.ui.clear(); + this.previewVisible = true; + }); + } + + openStructuredDiff() { + if (!this.compareReady || this.compareBusy || !this.compareLeftId || !this.compareRightId) { + return; + } + this.compareBusy = true; + const [left, right] = this.sortPairByDate(this.compareLeftId, this.compareRightId); + this.lastCompared = { left, right }; + this.api.http.get(`${this.api.baseUrl}/backups/${left}/diff/${right}`).subscribe({ + next: (response) => { + this.diffData = response; + this.ui.clear(); + this.diffVisible = true; + }, + complete: () => { + this.compareBusy = false; + } + }); + } + + openHtmlDiff() { + if (!this.lastCompared) { + return; + } + this.api.http + .get(`${this.api.baseUrl}/backups/${this.lastCompared.left}/diff/${this.lastCompared.right}/html`, { responseType: 'text' }) + .subscribe((html) => { + const blob = new Blob([html], { type: 'text/html;charset=utf-8' }); + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + setTimeout(() => URL.revokeObjectURL(url), 60_000); + }); + } + + relativeAge(value: string): string { + const diff = Date.now() - new Date(value).getTime(); + const hours = Math.floor(diff / 3_600_000); + if (hours < 1) { + const minutes = Math.max(1, Math.floor(diff / 60_000)); + return this.ui.instant('files.minutesAgo', { value: minutes }); + } + if (hours < 24) { + return this.ui.instant('files.hoursAgo', { value: hours }); + } + const days = Math.floor(hours / 24); + return this.ui.instant('files.daysAgo', { value: days }); + } + + checksumShort(value?: string | null): string { + if (!value) { + return 'n/a'; + } + return `${value.slice(0, 8)}…${value.slice(-6)}`; + } + + private sortPairByDate(firstId: number, secondId: number): [number, number] { + const first = this.files.find((item) => item.id === firstId); + const second = this.files.find((item) => item.id === secondId); + if (!first || !second) { + return [firstId, secondId]; + } + return new Date(first.created_at).getTime() <= new Date(second.created_at).getTime() ? [firstId, secondId] : [secondId, firstId]; + } +} diff --git a/frontend/src/app/features/files/files-page.component.html b/frontend/src/app/features/files/files-page.component.html new file mode 100644 index 0000000..3d40990 --- /dev/null +++ b/frontend/src/app/features/files/files-page.component.html @@ -0,0 +1,191 @@ + +
+ + +
+
+ +
+ + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ +
+
+
+ {{ 'files.compareTitle' | translate }} +

{{ 'files.compareSubtitle' | translate }}

+
+
+ + +
+
+ +
+
+ + +
+ + + +
+ + +
+ +
+ + +
+
+
+
+ + + + + + + {{ 'files.fileColumn' | translate }} + {{ 'files.routerColumn' | translate }} + {{ 'files.typeColumn' | translate }} + {{ 'files.createdColumn' | translate }} + {{ 'files.sizeColumn' | translate }} + {{ 'files.compareColumn' | translate }} + {{ 'files.actionsColumn' | translate }} + + + + + + +
{{ item.file_name }}
+ {{ 'files.checksum' | translate }}: {{ checksumShort(item.checksum) }} + + +
{{ item.router_name || item.router_id }}
+ {{ deviceLabel(item) }} · ID {{ item.router_id }} + + + +
{{ item.created_at | date: 'dd.MM.yyyy HH:mm' }}
+ {{ relativeAge(item.created_at) }} + + +
{{ formatBytes(item.file_size) }}
+ {{ fileExtension(item) }} + + +
+ + + +
+ + {{ 'files.binaryNoCompare' | translate }} + + + +
+ + + + + +
+ + +
+
+
+ + +
{{ viewedExport }}
+
+ + +
+
+
+
{{ diff.left_file_name }}
+ {{ 'files.compareOlder' | translate }} +
+
+
+
{{ diff.right_file_name }}
+ {{ 'files.compareNewer' | translate }} +
+
+ +{{ diff.stats.added }} + -{{ diff.stats.removed }} + ~{{ diff.stats.modified }} +
+
+ + +
+
+ +
+
+
+ {{ line.left_number || '' }} +
{{ line.left_text || ' ' }}
+
+
+ {{ line.right_number || '' }} +
{{ line.right_text || ' ' }}
+
+
+
+ + +
{{ diff.diff_text }}
+
+
+
diff --git a/frontend/src/app/features/files/files-page.component.ts b/frontend/src/app/features/files/files-page.component.ts new file mode 100644 index 0000000..dc876ad --- /dev/null +++ b/frontend/src/app/features/files/files-page.component.ts @@ -0,0 +1,457 @@ +import { CommonModule } from '@angular/common'; +import { HttpParams, HttpResponse } from '@angular/common/http'; +import { Component, OnInit, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { SelectModule } from 'primeng/select'; +import { InputTextModule } from 'primeng/inputtext'; +import { TableModule } from 'primeng/table'; +import { TagModule } from 'primeng/tag'; + +import { ApiService } from '../../core/services/api.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; +import { SectionCardComponent } from '../../shared/ui/section-card.component'; +import { StatCardComponent } from '../../shared/ui/stat-card.component'; + +type DeviceType = 'routeros' | 'switchos'; + +interface BackupFile { + id: number; + router_id: number; + router_name?: string; + device_type: DeviceType; + file_name: string; + backup_type: 'export' | 'binary'; + created_at: string; + checksum?: string | null; + file_size?: number | null; +} + +interface BackupDiffLine { + type: 'context' | 'added' | 'removed' | 'modified'; + left_number?: number | null; + right_number?: number | null; + left_text: string; + right_text: string; +} + +interface BackupDiffStats { + added: number; + removed: number; + modified: number; + context: number; +} + +interface BackupDiffResponse { + left_backup_id: number; + right_backup_id: number; + left_file_name?: string | null; + right_file_name?: string | null; + diff_text: string; + lines: BackupDiffLine[]; + stats?: BackupDiffStats | null; +} + +@Component({ + standalone: true, + imports: [CommonModule, FormsModule, TranslateModule, ButtonModule, DialogModule, SelectModule, InputTextModule, TableModule, TagModule, PageHeaderComponent, SectionCardComponent, StatCardComponent], + templateUrl: './files-page.component.html' +}) +export class FilesPageComponent implements OnInit { + private readonly api = inject(ApiService); + private readonly ui = inject(UiService); + + files: BackupFile[] = []; + selected: BackupFile[] = []; + routers: { id: number; name: string }[] = []; + search = ''; + backupType: 'export' | 'binary' | '' = ''; + routerId: number | null = null; + createdOn = ''; + sortBy = 'created_at'; + order: 'asc' | 'desc' = 'desc'; + diffText = ''; + viewedExport = ''; + previewTitle = ''; + loading = false; + bulkBusy = false; + compareBusy = false; + previewVisible = false; + diffVisible = false; + compareLeftId: number | null = null; + compareRightId: number | null = null; + diffData: BackupDiffResponse | null = null; + lastCompared: { left: number; right: number } | null = null; + + get typeOptions() { + return [ + { label: this.ui.instant('files.allTypes'), value: '' }, + { label: this.ui.instant('files.exportType'), value: 'export' }, + { label: this.ui.instant('files.binaryType'), value: 'binary' } + ]; + } + + get sortOptions() { + return [ + { label: this.ui.instant('files.sortNewest'), value: 'created_at' }, + { label: this.ui.instant('files.sortName'), value: 'file_name' }, + { label: this.ui.instant('files.sortRouter'), value: 'router_name' }, + { label: this.ui.instant('files.sortType'), value: 'backup_type' } + ]; + } + + get orderOptions() { + return [ + { label: this.ui.instant('common.desc'), value: 'desc' }, + { label: this.ui.instant('common.asc'), value: 'asc' } + ]; + } + + get routerOptions() { + return [{ label: this.ui.instant('files.allRouters'), value: null }, ...this.routers.map((r) => ({ label: r.name, value: r.id }))]; + } + + get compareOptions() { + return this.exportFiles.map((item) => ({ + label: `${item.router_name || item.router_id} · ${item.file_name}`, + value: item.id + })); + } + + get exportFiles(): BackupFile[] { + return this.files + .filter((item) => item.backup_type === 'export') + .slice() + .sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()); + } + + get selectedIds(): number[] { + return this.selected.map((item) => item.id); + } + + get exportCount(): number { + return this.files.filter((item) => item.backup_type === 'export').length; + } + + get binaryCount(): number { + return this.files.filter((item) => item.backup_type === 'binary').length; + } + + get compareLeft(): BackupFile | undefined { + return this.files.find((item) => item.id === this.compareLeftId); + } + + get compareRight(): BackupFile | undefined { + return this.files.find((item) => item.id === this.compareRightId); + } + + get compareReady(): boolean { + return !!this.compareLeftId && !!this.compareRightId && this.compareLeftId !== this.compareRightId; + } + + get compareContextLabel(): string { + if (!this.compareReady) { + return this.ui.instant('files.compareSelectionHint'); + } + const left = this.compareLeft; + const right = this.compareRight; + const routerName = left?.router_name || right?.router_name || ''; + if (left && right && left.router_id === right.router_id) { + return this.ui.instant('files.compareReadySameRouter', { router: routerName || left.router_id }); + } + return this.ui.instant('files.compareReadyMixedRouters'); + } + + compareRole(item: BackupFile): 'left' | 'right' | '' { + if (item.id === this.compareLeftId) { + return 'left'; + } + if (item.id === this.compareRightId) { + return 'right'; + } + return ''; + } + + ngOnInit() { + this.api.http.get(`${this.api.baseUrl}/routers`).subscribe((routers) => { + this.routers = routers.map((item) => ({ id: item.id, name: item.name })); + }); + this.load(); + } + + load() { + this.loading = true; + let params = new HttpParams().set('sort_by', this.sortBy).set('order', this.order); + if (this.search.trim()) params = params.set('search', this.search.trim()); + if (this.backupType) params = params.set('backup_type', this.backupType); + if (this.routerId !== null) params = params.set('router_id', String(this.routerId)); + if (this.createdOn) params = params.set('created_on', this.createdOn); + + this.api.http.get(`${this.api.baseUrl}/backups`, { params }).subscribe({ + next: (files) => { + this.files = files; + this.selected = []; + if (this.compareLeftId && !this.files.some((item) => item.id === this.compareLeftId)) { + this.compareLeftId = null; + } + if (this.compareRightId && !this.files.some((item) => item.id === this.compareRightId)) { + this.compareRightId = null; + } + }, + complete: () => { + this.loading = false; + } + }); + } + + resetFilters() { + this.search = ''; + this.backupType = ''; + this.routerId = null; + this.createdOn = ''; + this.sortBy = 'created_at'; + this.order = 'desc'; + this.load(); + } + + download(id: number) { + this.api.http + .get(`${this.api.baseUrl}/backups/${id}/download`, { observe: 'response', responseType: 'blob' }) + .subscribe((response) => this.openBlob(response, `backup-${id}`)); + } + + viewExport(item: BackupFile) { + this.api.http.get<{ content: string }>(`${this.api.baseUrl}/backups/${item.id}/view`).subscribe((response) => { + this.viewedExport = response.content; + this.previewTitle = item.file_name; + this.ui.clear(); + this.previewVisible = true; + }); + } + + sendEmail(id: number) { + this.api.http.post(`${this.api.baseUrl}/backups/${id}/email`, {}).subscribe(() => { + this.ui.success('toast.backupSentEmail'); + }); + } + + upload(item: BackupFile) { + if (item.device_type !== 'routeros') { + return; + } + this.api.http.post(`${this.api.baseUrl}/backups/router/${item.router_id}/upload/${item.id}`, {}).subscribe(() => { + this.ui.success('toast.binaryUploaded'); + }); + } + + async deleteOne(id: number) { + const accepted = await this.ui.confirm({ + messageKey: 'confirm.deleteBackup', + acceptKey: 'common.delete' + }); + if (!accepted) { + return; + } + this.api.http.delete(`${this.api.baseUrl}/backups/${id}`).subscribe(() => { + this.ui.success('toast.backupDeleted'); + this.load(); + }); + } + + assignCompare(side: 'left' | 'right', item: BackupFile) { + if (item.backup_type !== 'export') { + return; + } + if (side === 'left') { + this.compareLeftId = item.id; + } else { + this.compareRightId = item.id; + } + } + + fillLatestPair() { + if (this.exportFiles.length < 2) { + return; + } + const latest = this.exportFiles[this.exportFiles.length - 1]; + const previous = this.exportFiles[this.exportFiles.length - 2]; + this.compareLeftId = previous.id; + this.compareRightId = latest.id; + } + + compareClosestForRouter(item: BackupFile) { + const candidates = this.exportFiles.filter((entry) => entry.router_id === item.router_id); + if (candidates.length < 2) { + return; + } + const targetIndex = candidates.findIndex((entry) => entry.id === item.id); + const older = candidates[Math.max(0, targetIndex - 1)]; + const newer = candidates[Math.min(candidates.length - 1, targetIndex + 1)]; + const left = older && older.id !== item.id ? older : item; + const right = newer && newer.id !== item.id ? newer : item; + if (left.id === right.id) { + return; + } + this.setComparePair(left.id, right.id); + this.openStructuredDiff(); + } + + swapCompare() { + const left = this.compareLeftId; + this.compareLeftId = this.compareRightId; + this.compareRightId = left; + } + + openStructuredDiff() { + if (!this.compareReady || this.compareBusy || !this.compareLeftId || !this.compareRightId) { + return; + } + this.compareBusy = true; + const [left, right] = this.sortPairByDate(this.compareLeftId, this.compareRightId); + this.lastCompared = { left, right }; + this.api.http.get(`${this.api.baseUrl}/backups/${left}/diff/${right}`).subscribe({ + next: (response) => { + this.diffData = response; + this.diffText = response.diff_text; + this.ui.clear(); + this.diffVisible = true; + }, + complete: () => { + this.compareBusy = false; + } + }); + } + + openHtmlDiff() { + if (!this.lastCompared) { + return; + } + this.api.http + .get(`${this.api.baseUrl}/backups/${this.lastCompared.left}/diff/${this.lastCompared.right}/html`, { responseType: 'text' }) + .subscribe((html) => { + const blob = new Blob([html], { type: 'text/html;charset=utf-8' }); + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + setTimeout(() => URL.revokeObjectURL(url), 60_000); + }); + } + + async bulkDelete() { + if (!this.selectedIds.length || this.bulkBusy) { + return; + } + const accepted = await this.ui.confirm({ + messageKey: 'confirm.deleteSelectedFiles', + params: { count: this.selectedIds.length }, + acceptKey: 'common.delete' + }); + if (!accepted) { + return; + } + this.bulkBusy = true; + this.api.http.post(`${this.api.baseUrl}/backups/bulk`, { action: 'delete', backup_ids: this.selectedIds }).subscribe({ + next: () => { + this.ui.success('toast.selectedBackupsDeleted'); + this.load(); + }, + complete: () => { + this.bulkBusy = false; + } + }); + } + + bulkDownload() { + if (!this.selectedIds.length || this.bulkBusy) { + return; + } + this.bulkBusy = true; + this.api.http + .post(`${this.api.baseUrl}/backups/bulk`, { action: 'download', backup_ids: this.selectedIds }, { responseType: 'blob' }) + .subscribe({ + next: (blob: Blob) => { + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = 'routeros-backups.zip'; + link.click(); + setTimeout(() => URL.revokeObjectURL(url), 60_000); + this.ui.success('toast.archivePrepared'); + }, + complete: () => { + this.bulkBusy = false; + } + }); + } + + formatBytes(value?: number | null): string { + if (!value) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let size = value; + let unit = 0; + while (size >= 1024 && unit < units.length - 1) { + size /= 1024; + unit += 1; + } + return `${size.toFixed(size >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`; + } + + relativeAge(value: string): string { + const diff = Date.now() - new Date(value).getTime(); + const hours = Math.floor(diff / 3_600_000); + if (hours < 1) { + const minutes = Math.max(1, Math.floor(diff / 60_000)); + return this.ui.instant('files.minutesAgo', { value: minutes }); + } + if (hours < 24) { + return this.ui.instant('files.hoursAgo', { value: hours }); + } + const days = Math.floor(hours / 24); + return this.ui.instant('files.daysAgo', { value: days }); + } + + checksumShort(value?: string | null): string { + if (!value) { + return 'n/a'; + } + return `${value.slice(0, 8)}…${value.slice(-6)}`; + } + + deviceLabel(item: BackupFile): string { + return this.ui.instant(item.device_type === 'switchos' ? 'routers.switchos' : 'routers.routeros'); + } + + fileExtension(item: BackupFile): string { + const parts = item.file_name.split('.'); + return parts.length > 1 ? `.${parts[parts.length - 1]}` : '—'; + } + + private setComparePair(firstId: number, secondId: number) { + const [left, right] = this.sortPairByDate(firstId, secondId); + this.compareLeftId = left; + this.compareRightId = right; + } + + private sortPairByDate(firstId: number, secondId: number): [number, number] { + const first = this.files.find((item) => item.id === firstId); + const second = this.files.find((item) => item.id === secondId); + if (!first || !second) { + return [firstId, secondId]; + } + return new Date(first.created_at).getTime() <= new Date(second.created_at).getTime() ? [firstId, secondId] : [secondId, firstId]; + } + + private openBlob(response: HttpResponse, fallbackName: string) { + const disposition = response.headers.get('content-disposition') || ''; + const match = disposition.match(/filename="?([^";]+)"?/i); + const filename = match?.[1] || fallbackName; + const url = URL.createObjectURL(response.body || new Blob()); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.click(); + setTimeout(() => URL.revokeObjectURL(url), 60_000); + } +} diff --git a/frontend/src/app/features/logs/logs-page.component.html b/frontend/src/app/features/logs/logs-page.component.html new file mode 100644 index 0000000..22024d8 --- /dev/null +++ b/frontend/src/app/features/logs/logs-page.component.html @@ -0,0 +1,25 @@ + +
+ + +
+
+ +
+
+ {{ retentionDays }} {{ 'logs.daysSuffix' | translate }} + {{ 'logs.retentionInfoLabel' | translate }} +
+
+ + + + {{ 'logs.timestampColumn' | translate }}{{ 'logs.messageColumn' | translate }} + + + {{ log.timestamp }} +
{{ log.message }}
+ +
+
+
diff --git a/frontend/src/app/features/logs/logs-page.component.ts b/frontend/src/app/features/logs/logs-page.component.ts new file mode 100644 index 0000000..4cb3cd8 --- /dev/null +++ b/frontend/src/app/features/logs/logs-page.component.ts @@ -0,0 +1,63 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnInit, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { InputTextModule } from 'primeng/inputtext'; +import { TableModule } from 'primeng/table'; + +import { ApiService } from '../../core/services/api.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; +import { SectionCardComponent } from '../../shared/ui/section-card.component'; + +@Component({ + standalone: true, + imports: [CommonModule, FormsModule, TranslateModule, ButtonModule, InputTextModule, TableModule, PageHeaderComponent, SectionCardComponent], + templateUrl: './logs-page.component.html' +}) +export class LogsPageComponent implements OnInit { + private readonly api = inject(ApiService); + private readonly ui = inject(UiService); + + logs: any[] = []; + days = 7; + retentionDays = 7; + cleaning = false; + + ngOnInit() { + this.load(); + this.api.http.get(`${this.api.baseUrl}/settings`).subscribe((settings) => { + this.retentionDays = Number(settings?.log_retention_days || 7); + this.days = this.retentionDays; + }); + } + + load() { + this.api.http.get(`${this.api.baseUrl}/logs`).subscribe((r) => (this.logs = r)); + } + + async cleanup() { + if (this.cleaning) { + return; + } + const accepted = await this.ui.confirm({ + messageKey: 'confirm.deleteLogsOlderThan', + params: { days: this.days }, + acceptKey: 'common.delete' + }); + if (!accepted) { + return; + } + this.cleaning = true; + this.api.http.delete(`${this.api.baseUrl}/logs/older-than/${this.days}`).subscribe({ + next: () => { + this.ui.success('toast.logsDeletedOlderThan', { days: this.days }); + this.load(); + }, + complete: () => { + this.cleaning = false; + } + }); + } +} diff --git a/frontend/src/app/features/routers/router-detail-page.component.html b/frontend/src/app/features/routers/router-detail-page.component.html new file mode 100644 index 0000000..79a47c9 --- /dev/null +++ b/frontend/src/app/features/routers/router-detail-page.component.html @@ -0,0 +1,170 @@ + +
+ + + + +
+
+ +
+ + + + +
+ +
+ +
+
+
{{ 'routers.connectionStateTitle' | translate }}{{ connection.success ? ('common.ok' | translate) : ('common.failed' | translate) }}
+
{{ 'routers.lastTestAt' | translate }}{{ connection.tested_at | date:'short' }}
+
{{ 'routers.hostname' | translate }}{{ connection.hostname }}
+
{{ 'routers.model' | translate }}{{ connection.model }}
+
{{ 'routers.version' | translate }}{{ connection.version || 'n/a' }}
+
{{ 'routers.uptime' | translate }}{{ connection.uptime }}
+
{{ 'routers.httpStatus' | translate }}{{ connection.http_status || '—' }}
+
{{ 'routers.serverHeader' | translate }}{{ connection.server || '—' }}
+
{{ 'routers.authMode' | translate }}{{ connection.auth_mode || '—' }}
+
{{ 'routers.backupEndpoint' | translate }}{{ connection.backup_available ? ('routers.backupAvailable' | translate) : ('routers.backupUnavailable' | translate) }}
+
+
+ {{ 'routers.lastError' | translate }} + {{ connection.error }} +
+
+ +
+ +

{{ 'routers.noConnection' | translate }}

+
+
+
+ +
+ +
+
+ {{ previewTitle }} + {{ 'routers.previewModalHint' | translate }} +
+
+ +
+
+ +
+ +

{{ 'routers.noPreview' | translate }}

+
+
+
+ + +
+
+ {{ diffData.left_file_name }} → {{ diffData.right_file_name }} + {{ 'routers.diffModalHint' | translate }} +
+
+ +
+
+ +
+ +

{{ 'routers.noDiff' | translate }}

+
+
+
+
+
+ +
+ + + + {{ 'files.fileColumn' | translate }}{{ 'files.createdColumn' | translate }}{{ 'common.actions' | translate }} + + + + +
{{ item.file_name }}
+ {{ 'files.exportType' | translate }} + + {{ item.created_at }} + +
+ + + + + +
+ + +
+
+
+
+ +
+ + + + {{ 'files.fileColumn' | translate }}{{ 'files.createdColumn' | translate }}{{ 'common.actions' | translate }} + + + + +
{{ item.file_name }}
+ {{ 'files.binaryType' | translate }} + + {{ item.created_at }} + +
+ + + + +
+ + +
+
+
+
+ + +
{{ exportContent }}
+
+ + +
+
+
+
{{ diff.left_file_name }}
+ {{ 'files.compareOlder' | translate }} +
+
+
+
{{ diff.right_file_name }}
+ {{ 'files.compareNewer' | translate }} +
+
+ +{{ diff.stats.added }} + -{{ diff.stats.removed }} + ~{{ diff.stats.modified }} +
+
+
{{ diff.diff_text }}
+
+ +
{{ diffText }}
+
+
diff --git a/frontend/src/app/features/routers/router-detail-page.component.ts b/frontend/src/app/features/routers/router-detail-page.component.ts new file mode 100644 index 0000000..c37b5e4 --- /dev/null +++ b/frontend/src/app/features/routers/router-detail-page.component.ts @@ -0,0 +1,346 @@ +import { CommonModule } from '@angular/common'; +import { HttpResponse } from '@angular/common/http'; +import { Component, OnInit, inject } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { TableModule } from 'primeng/table'; +import { TagModule } from 'primeng/tag'; + +import { ApiService } from '../../core/services/api.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; +import { SectionCardComponent } from '../../shared/ui/section-card.component'; +import { StatCardComponent } from '../../shared/ui/stat-card.component'; + +type DeviceType = 'routeros' | 'switchos'; + +interface DeviceItem { + id: number; + name: string; + host: string; + port: number; + device_type: DeviceType; + effective_username?: string | null; + supports_export: boolean; + supports_restore_upload: boolean; + last_connection_status?: boolean | null; + last_connection_tested_at?: string | null; + last_connection_error?: string | null; + last_connection_hostname?: string | null; + last_connection_model?: string | null; + last_connection_version?: string | null; + last_connection_uptime?: string | null; + last_connection_transport?: string | null; + last_connection_server?: string | null; + last_connection_auth_mode?: string | null; + last_connection_http_status?: string | null; + last_connection_backup_available?: boolean | null; +} + +interface BackupItem { + id: number; + file_name: string; + backup_type: 'export' | 'binary'; + created_at: string; + device_type: DeviceType; +} + +interface ConnectionSnapshot { + success: boolean; + tested_at: string; + hostname: string; + model: string; + version?: string | null; + uptime: string; + error?: string | null; + transport?: string | null; + server?: string | null; + auth_mode?: string | null; + http_status?: string | null; + backup_available?: boolean | null; +} + +interface BackupDiffStats { + added: number; + removed: number; + modified: number; + context: number; +} + +interface BackupDiffResponse { + left_backup_id: number; + right_backup_id: number; + left_file_name?: string | null; + right_file_name?: string | null; + diff_text: string; + stats?: BackupDiffStats | null; +} + +@Component({ + standalone: true, + imports: [CommonModule, TranslateModule, ButtonModule, DialogModule, TableModule, TagModule, PageHeaderComponent, SectionCardComponent, StatCardComponent], + templateUrl: './router-detail-page.component.html' +}) +export class RouterDetailPageComponent implements OnInit { + private readonly route = inject(ActivatedRoute); + private readonly api = inject(ApiService); + private readonly router = inject(Router); + private readonly ui = inject(UiService); + + routerId!: number; + routerItem: DeviceItem | null = null; + backups: BackupItem[] = []; + connection: ConnectionSnapshot | null = null; + exportContent = ''; + diffText = ''; + previewTitle = ''; + previewVisible = false; + diffVisible = false; + diffData: BackupDiffResponse | null = null; + exporting = false; + runningBinary = false; + testing = false; + deletingRouter = false; + + get isSwitchos(): boolean { + return this.routerItem?.device_type === 'switchos'; + } + + get exportBackups(): BackupItem[] { + return this.backups.filter((item) => item.backup_type === 'export'); + } + + get binaryBackups(): BackupItem[] { + return this.backups.filter((item) => item.backup_type === 'binary'); + } + + get connectionStateLabel(): string { + if (!this.connection) { + return this.ui.instant('common.idle'); + } + return this.connection.success ? this.ui.instant('common.ok') : this.ui.instant('common.failed'); + } + + get hasPreview(): boolean { + return !!this.exportContent; + } + + get hasDiff(): boolean { + return !!this.diffText; + } + + get subtitle(): string { + if (!this.routerItem) { + return this.ui.instant('routers.detailSubtitle'); + } + const suffix = this.routerItem.effective_username ? ` · ${this.routerItem.effective_username}` : ''; + return `${this.routerItem.host}:${this.routerItem.port}${suffix}`; + } + + get deviceTypeLabel(): string { + return this.ui.instant(this.isSwitchos ? 'routers.switchos' : 'routers.routeros'); + } + + ngOnInit() { + this.routerId = Number(this.route.snapshot.paramMap.get('id')); + this.load(); + } + + load() { + this.api.http.get(`${this.api.baseUrl}/routers/${this.routerId}`).subscribe((routerItem) => { + this.routerItem = routerItem; + this.connection = this.mapStoredConnection(routerItem); + }); + this.api.http.get(`${this.api.baseUrl}/backups/router/${this.routerId}`).subscribe((r) => (this.backups = r)); + } + + runExport() { + if (this.exporting || this.isSwitchos) { + return; + } + this.exporting = true; + this.api.http.post(`${this.api.baseUrl}/backups/router/${this.routerId}/export`, {}).subscribe({ + next: () => { + this.ui.success('toast.exportCreated'); + this.load(); + }, + complete: () => { + this.exporting = false; + } + }); + } + + runBinary() { + if (this.runningBinary) { + return; + } + this.runningBinary = true; + this.api.http.post(`${this.api.baseUrl}/backups/router/${this.routerId}/binary`, {}).subscribe({ + next: () => { + this.ui.success('toast.binaryCreated'); + this.load(); + }, + complete: () => { + this.runningBinary = false; + } + }); + } + + testConnection() { + if (this.testing) { + return; + } + this.testing = true; + this.api.http.get(`${this.api.baseUrl}/routers/${this.routerId}/test-connection`).subscribe({ + next: (result) => { + this.connection = result; + this.syncStoredConnection(result); + if (result.success) { + this.ui.success('toast.connectionSuccessful'); + } else { + this.ui.error('toast.connectionFailed'); + } + }, + complete: () => { + this.testing = false; + } + }); + } + + compareToLatest(id: number) { + const latest = this.exportBackups[0]; + if (!latest || latest.id === id) { + return; + } + this.api.http.get(`${this.api.baseUrl}/backups/${id}/diff/${latest.id}`).subscribe((response) => { + this.diffData = response; + this.diffText = response.diff_text; + this.ui.clear(); + this.diffVisible = true; + }); + } + + async remove(id: number) { + const accepted = await this.ui.confirm({ messageKey: 'confirm.deleteBackup', acceptKey: 'common.delete' }); + if (!accepted) { + return; + } + this.api.http.delete(`${this.api.baseUrl}/backups/${id}`).subscribe(() => { + this.ui.success('toast.backupDeleted'); + this.load(); + }); + } + + upload(id: number) { + if (this.isSwitchos) { + return; + } + this.api.http.post(`${this.api.baseUrl}/backups/router/${this.routerId}/upload/${id}`, {}).subscribe(() => { + this.ui.success('toast.binaryUploaded'); + }); + } + + async deleteRouter() { + if (this.deletingRouter) { + return; + } + const accepted = await this.ui.confirm({ messageKey: 'confirm.deleteRouterWithFiles', acceptKey: 'common.delete' }); + if (!accepted) { + return; + } + this.deletingRouter = true; + this.api.http.delete(`${this.api.baseUrl}/routers/${this.routerId}`).subscribe({ + next: () => this.router.navigate(['/devices']), + complete: () => { + this.deletingRouter = false; + } + }); + } + + download(id: number) { + this.api.http + .get(`${this.api.baseUrl}/backups/${id}/download`, { observe: 'response', responseType: 'blob' }) + .subscribe((response) => this.openBlob(response, `backup-${id}`)); + } + + viewExport(id: number) { + const backup = this.exportBackups.find((item) => item.id === id); + this.api.http.get<{ content: string }>(`${this.api.baseUrl}/backups/${id}/view`).subscribe((r) => { + this.exportContent = r.content; + this.previewTitle = backup?.file_name || this.ui.instant('routers.previewTitle'); + this.ui.clear(); + this.previewVisible = true; + }); + } + + sendEmail(id: number) { + this.api.http.post(`${this.api.baseUrl}/backups/${id}/email`, {}).subscribe(() => { + this.ui.success('toast.backupSentEmail'); + }); + } + + openPreviewModal() { + this.ui.clear(); + this.previewVisible = true; + } + + openDiffModal() { + this.ui.clear(); + this.diffVisible = true; + } + + private mapStoredConnection(routerItem: DeviceItem): ConnectionSnapshot | null { + if (!routerItem?.last_connection_tested_at) { + return null; + } + return { + success: Boolean(routerItem.last_connection_status), + tested_at: routerItem.last_connection_tested_at, + hostname: routerItem.last_connection_hostname || routerItem.name, + model: routerItem.last_connection_model || 'Unknown', + version: routerItem.last_connection_version, + uptime: routerItem.last_connection_uptime || 'Unknown', + error: routerItem.last_connection_error || null, + transport: routerItem.last_connection_transport || null, + server: routerItem.last_connection_server || null, + auth_mode: routerItem.last_connection_auth_mode || null, + http_status: routerItem.last_connection_http_status || null, + backup_available: routerItem.last_connection_backup_available ?? null + }; + } + + private syncStoredConnection(result: ConnectionSnapshot) { + if (!this.routerItem) { + return; + } + this.routerItem = { + ...this.routerItem, + last_connection_status: result.success, + last_connection_tested_at: result.tested_at, + last_connection_hostname: result.hostname, + last_connection_model: result.model, + last_connection_version: result.version, + last_connection_uptime: result.uptime, + last_connection_error: result.error, + last_connection_transport: result.transport, + last_connection_server: result.server, + last_connection_auth_mode: result.auth_mode, + last_connection_http_status: result.http_status, + last_connection_backup_available: result.backup_available + }; + } + + private openBlob(response: HttpResponse, fallbackName: string) { + const disposition = response.headers.get('content-disposition') || ''; + const match = disposition.match(/filename="?([^";]+)"?/i); + const filename = match?.[1] || fallbackName; + const url = URL.createObjectURL(response.body || new Blob()); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.click(); + setTimeout(() => URL.revokeObjectURL(url), 60_000); + } +} diff --git a/frontend/src/app/features/routers/routers-page.component.html b/frontend/src/app/features/routers/routers-page.component.html new file mode 100644 index 0000000..5a36481 --- /dev/null +++ b/frontend/src/app/features/routers/routers-page.component.html @@ -0,0 +1,156 @@ + +
+ +
+
+ +
+
+ {{ routers.length }} + {{ 'routers.registeredDevices' | translate }} +
+
+
+ {{ routerOsCount }} + {{ 'routers.routeros' | translate }} +
+
+
+ {{ switchOsCount }} + {{ 'routers.switchos' | translate }} +
+
+ + + + + {{ 'routers.name' | translate }}{{ 'routers.endpoint' | translate }}{{ 'routers.access' | translate }}{{ 'common.actions' | translate }} + + + + +
{{ routerItem.name }}
+ {{ deviceTypeLabel(routerItem) }} + + +
{{ routerItem.host }}:{{ routerItem.port }}
+ {{ accessUser(routerItem) }} + + +
+ + +
+ + +
+ + + +
+ + +
+
+
+ + + +
+
+ +
+
+
+ {{ 'routers.deviceType' | translate }} · {{ selectedDeviceType === 'switchos' ? ('routers.switchos' | translate) : ('routers.routeros' | translate) }} +
+
{{ dialogTitle }}
+ + {{ + selectedDeviceType === 'switchos' + ? ('routers.switchDialogSubtitle' | translate) + : ('routers.routerDialogSubtitle' | translate) + }} + +
+
+
+ +
+
+
+
+ {{ 'routers.connectionSectionTitle' | translate }} +

{{ 'routers.connectionSectionHint' | translate }}

+
+ + + {{ selectedDeviceType === 'switchos' ? 'HTTP' : 'SSH' }} + +
+ +
+ + + + + + + + + + + + + + + + +
+
+ +
+
+
+ {{ 'routers.credentialsSectionTitle' | translate }} +

+ {{ + selectedDeviceType === 'switchos' + ? ('routers.switchDialogSubtitle' | translate) + : ('routers.routerDialogSubtitle' | translate) + }} +

+
+ + + {{ selectedDeviceType === 'switchos' ? ('routers.defaultCredentials' | translate) : 'SSH' }} + +
+ +
+ + + + + + + + + + + + +
+ +
+ + {{ 'routers.switchDefaultsHint' | translate }} +
+
+ +
+ + +
+
+
diff --git a/frontend/src/app/features/routers/routers-page.component.ts b/frontend/src/app/features/routers/routers-page.component.ts new file mode 100644 index 0000000..8e9ed81 --- /dev/null +++ b/frontend/src/app/features/routers/routers-page.component.ts @@ -0,0 +1,217 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnInit, inject } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { SelectModule } from 'primeng/select'; +import { TextareaModule } from 'primeng/textarea'; +import { InputTextModule } from 'primeng/inputtext'; +import { TableModule } from 'primeng/table'; +import { TagModule } from 'primeng/tag'; + +import { ApiService } from '../../core/services/api.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; +import { SectionCardComponent } from '../../shared/ui/section-card.component'; + +type DeviceType = 'routeros' | 'switchos'; + +interface RouterItem { + id: number; + name: string; + host: string; + port: number; + device_type: DeviceType; + ssh_user?: string | null; + ssh_password?: string | null; + ssh_key?: string | null; + effective_username?: string | null; + uses_global_ssh_key?: boolean; + has_effective_ssh_key?: boolean; + uses_global_switchos_credentials?: boolean; + has_effective_password?: boolean; +} + +@Component({ + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + TranslateModule, + ButtonModule, + DialogModule, + SelectModule, + InputTextModule, + TextareaModule, + TableModule, + TagModule, + PageHeaderComponent, + SectionCardComponent + ], + templateUrl: './routers-page.component.html' +}) +export class RoutersPageComponent implements OnInit { + private readonly api = inject(ApiService); + private readonly fb = inject(FormBuilder); + private readonly router = inject(Router); + private readonly ui = inject(UiService); + + visible = false; + editingId: number | null = null; + saving = false; + routers: RouterItem[] = []; + readonly deviceTypeOptions = [ + { label: 'RouterOS', value: 'routeros' }, + { label: 'SwitchOS', value: 'switchos' } + ]; + readonly form = this.fb.nonNullable.group({ + name: ['', Validators.required], + device_type: ['routeros' as DeviceType, Validators.required], + host: ['', Validators.required], + port: [22, Validators.required], + ssh_user: ['admin'], + ssh_password: '', + ssh_key: '' + }); + + get dialogTitle(): string { + return this.ui.instant(this.editingId ? 'routers.editDialogTitle' : 'routers.createDialogTitle'); + } + + get selectedDeviceType(): DeviceType { + return this.form.controls.device_type.value; + } + + get routerOsCount(): number { + return this.routers.filter((item) => item.device_type === 'routeros').length; + } + + get switchOsCount(): number { + return this.routers.filter((item) => item.device_type === 'switchos').length; + } + + ngOnInit() { + this.form.controls.device_type.valueChanges.subscribe((deviceType) => { + this.applyDeviceDefaults((deviceType || 'routeros') as DeviceType); + }); + this.load(); + } + + load() { + this.api.http.get(`${this.api.baseUrl}/routers`).subscribe((r) => (this.routers = r)); + } + + openCreate() { + this.editingId = null; + this.form.reset({ name: '', device_type: 'routeros', host: '', port: 22, ssh_user: 'admin', ssh_password: '', ssh_key: '' }); + this.visible = true; + } + + edit(item: RouterItem) { + this.editingId = item.id; + this.form.reset({ + name: item.name, + device_type: item.device_type, + host: item.host, + port: item.port, + ssh_user: item.ssh_user ?? '', + ssh_password: item.ssh_password ?? '', + ssh_key: item.ssh_key ?? '' + }); + this.visible = true; + } + + save() { + if (this.form.invalid || this.saving) { + return; + } + this.saving = true; + const payload = this.form.getRawValue(); + if (payload.device_type === 'switchos') { + payload.ssh_key = ''; + } + const request$ = this.editingId + ? this.api.http.put(`${this.api.baseUrl}/routers/${this.editingId}`, payload) + : this.api.http.post(`${this.api.baseUrl}/routers`, payload); + + request$.subscribe({ + next: () => { + this.ui.success(this.editingId ? 'toast.routerUpdated' : 'toast.routerCreated'); + this.visible = false; + this.load(); + }, + complete: () => { + this.saving = false; + } + }); + } + + async remove(id: number) { + const accepted = await this.ui.confirm({ + messageKey: 'confirm.deleteRouterWithFiles', + acceptKey: 'common.delete' + }); + if (!accepted) { + return; + } + this.api.http.delete(`${this.api.baseUrl}/routers/${id}`).subscribe(() => { + this.ui.success('toast.routerDeleted'); + this.load(); + }); + } + + open(id: number) { + this.router.navigate(['/devices', id]); + } + + deviceTypeLabel(item: RouterItem): string { + return this.ui.instant(item.device_type === 'switchos' ? 'routers.switchos' : 'routers.routeros'); + } + + accessUser(item: RouterItem): string { + return item.effective_username || item.ssh_user || '—'; + } + + primaryAccessTag(item: RouterItem): { value: string; severity: 'success' | 'warn' | 'secondary' | 'info' } { + if (item.device_type === 'switchos') { + if (item.uses_global_switchos_credentials) { + return { value: this.ui.instant('routers.defaultCredentials'), severity: 'info' }; + } + if (item.has_effective_password) { + return { value: this.ui.instant('routers.localCredentials'), severity: 'success' }; + } + return { value: this.ui.instant('routers.noCredentials'), severity: 'secondary' }; + } + + return { + value: item.ssh_password ? this.ui.instant('routers.passwordMode') : this.ui.instant('routers.noPassword'), + severity: item.ssh_password ? 'warn' : 'secondary' + }; + } + + secondaryAccessTag(item: RouterItem): { value: string; severity: 'success' | 'warn' | 'secondary' | 'info' } { + if (item.device_type === 'switchos') { + return { + value: item.has_effective_password ? this.ui.instant('routers.passwordMode') : this.ui.instant('routers.noPassword'), + severity: item.has_effective_password ? 'warn' : 'secondary' + }; + } + + return { + value: item.has_effective_ssh_key + ? this.ui.instant(item.uses_global_ssh_key ? 'routers.globalKeyMode' : 'routers.keyMode') + : this.ui.instant('routers.noKey'), + severity: item.has_effective_ssh_key ? 'success' : 'secondary' + }; + } + + private applyDeviceDefaults(deviceType: DeviceType) { + if (deviceType === 'switchos') { + this.form.patchValue({ port: 80, ssh_key: '', ssh_user: this.form.controls.ssh_user.value || '' }, { emitEvent: false }); + return; + } + this.form.patchValue({ port: 22, ssh_user: this.form.controls.ssh_user.value || 'admin' }, { emitEvent: false }); + } +} diff --git a/frontend/src/app/features/settings/settings-page.component.html b/frontend/src/app/features/settings/settings-page.component.html new file mode 100644 index 0000000..3136701 --- /dev/null +++ b/frontend/src/app/features/settings/settings-page.component.html @@ -0,0 +1,327 @@ + +
+ + +
+
+ +
+
+
+ {{ job.label | translate }} + +
+
+
{{ job.description | translate: job.description_params }}
+ {{ job.valid ? ((job.next_runs[0] | date:'short') || ('settings.noNextRun' | translate)) : job.error }} +
+
+
+ +
+
+
+
+ + {{ 'settings.automationTitle' | translate }} + {{ 'settings.automationSubtitle' | translate }} + +
+
+
+ {{ 'settings.automationPlannerTitle' | translate }} +

{{ 'settings.automationPlannerSubtitle' | translate }}

+
+ +
+ +
+
+
+
+ {{ 'settings.exportScheduleTitle' | translate }} + {{ scheduleSummary(scheduleEditors.export) }} +
+ +
+
{{ 'settings.exportPlannerHint' | translate }}
+
+ + + + + + +
+ + : + +
+
+ + + + + + + + +
+
+ +
+
+
+ {{ 'settings.binaryScheduleTitle' | translate }} + {{ scheduleSummary(scheduleEditors.binary) }} +
+ +
+
{{ 'settings.binaryPlannerHint' | translate }}
+
+ + + + + + +
+ + : + +
+
+ + + + + + + + +
+
+ +
+
+
+ {{ 'settings.retentionTitle' | translate }} + {{ scheduleSummary(scheduleEditors.retention) }} +
+ +
+
{{ 'settings.retentionPlannerHint' | translate }}
+
+ + + + + + + + + + + + + + +
+ + : + +
+
+ + + + + + + + +
+
+ +
+
+
+ {{ 'settings.connectionTestsTitle' | translate }} + {{ connectionTestSummary() }} +
+ +
+
{{ 'settings.connectionTestsHint' | translate }}
+
+ + + + +
+
+
+
+
+ +
+ + {{ 'settings.interfaceTitle' | translate }} + {{ 'settings.interfaceSubtitle' | translate }} + +
+
+
+ {{ 'settings.interfacePreferencesTitle' | translate }} +

{{ 'settings.interfacePreferencesHint' | translate }}

+
+ +
+ +
+ + + + + + + + +
+
+
+ +
+ + {{ 'settings.notificationsTitle' | translate }} + {{ 'settings.notificationsSubtitle' | translate }} + +
+
+
+
+ {{ 'settings.smtpEnabled' | translate }} + {{ 'settings.smtpEnabledHint' | translate }} +
+
+ + +
+
+
+
+ {{ 'settings.failuresOnly' | translate }} + {{ 'settings.failuresOnlyHint' | translate }} +
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+ + {{ 'settings.sshDefaultsTitle' | translate }} + {{ 'settings.sshDefaultsSubtitle' | translate }} + +
+
+
+
+ {{ 'settings.globalSshPrivateKey' | translate }} +

{{ 'settings.sshKeyHelper' | translate }}

+
+ + +
+ +
+

{{ 'settings.sshRevealHint' | translate }}

+
+ + +
+
+ +
+
+ +
+ + +
+ +
+ + +
+ + {{ 'settings.sshKeyClearNotice' | translate }} +
+ +
+
+
+ {{ 'settings.switchosDefaultsTitle' | translate }} +

{{ 'settings.switchosDefaultsHint' | translate }}

+
+
+
+ + + + + + + + +
+
+
+
+
+
+ +
+ +
+
diff --git a/frontend/src/app/features/settings/settings-page.component.ts b/frontend/src/app/features/settings/settings-page.component.ts new file mode 100644 index 0000000..f08a9d4 --- /dev/null +++ b/frontend/src/app/features/settings/settings-page.component.ts @@ -0,0 +1,499 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnDestroy, OnInit, effect, inject } from '@angular/core'; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ButtonModule } from 'primeng/button'; +import { SelectModule } from 'primeng/select'; +import { InputTextModule } from 'primeng/inputtext'; +import { TextareaModule } from 'primeng/textarea'; +import { TagModule } from 'primeng/tag'; +import { Subject, finalize, forkJoin, takeUntil } from 'rxjs'; + +import { ApiService } from '../../core/services/api.service'; +import { AuthService } from '../../core/services/auth.service'; +import { AppFont, FontService } from '../../core/services/font.service'; +import { APP_LANGUAGE_OPTIONS, AppLanguage, LanguageService } from '../../core/services/language.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; + +interface SchedulerJobStatus { + key: string; + label: string; + enabled: boolean; + cron?: string | null; + description: string; + description_params?: Record | null; + valid: boolean; + next_runs: string[]; + error?: string | null; +} + +interface SchedulerStatusResponse { + timezone: string; + running: boolean; + jobs: SchedulerJobStatus[]; +} + +type ScheduleMode = 'disabled' | 'daily' | 'weekly' | 'custom'; +type BooleanSettingControl = 'smtp_notifications_enabled' | 'notify_failures_only'; + +interface ScheduleEditor { + mode: ScheduleMode; + hour: string; + minute: string; + weekday: string; + cron: string; +} + +interface SettingsResponse { + backup_retention_days: number; + log_retention_days: number; + export_cron: string; + binary_cron: string; + retention_cron: string; + enable_auto_export: boolean; + connection_test_interval_minutes: number; + global_ssh_key: string | null; + has_global_ssh_key: boolean; + default_switchos_username: string | null; + default_switchos_password: string | null; + has_default_switchos_credentials: boolean; + pushover_token: string | null; + pushover_userkey: string | null; + notify_failures_only: boolean; + smtp_host: string | null; + smtp_port: number; + smtp_login: string | null; + smtp_password: string | null; + smtp_notifications_enabled: boolean; + recipient_email: string | null; +} + +@Component({ + standalone: true, + imports: [CommonModule, FormsModule, ReactiveFormsModule, TranslateModule, ButtonModule, SelectModule, InputTextModule, TextareaModule, TagModule, PageHeaderComponent], + templateUrl: './settings-page.component.html' +}) +export class SettingsPageComponent implements OnInit, OnDestroy { + private readonly fb = inject(FormBuilder); + private readonly api = inject(ApiService); + private readonly auth = inject(AuthService); + private readonly ui = inject(UiService); + private readonly language = inject(LanguageService); + private readonly font = inject(FontService); + private readonly destroy$ = new Subject(); + + saving = false; + testingEmail = false; + testingPushover = false; + unlockingSshKey = false; + schedulerStatus?: SchedulerStatusResponse; + hasStoredSshKey = false; + sshKeyVisible = false; + clearStoredSshKey = false; + sshRevealPassword = ''; + + readonly scheduleEditors: Record<'export' | 'binary' | 'retention', ScheduleEditor> = { + export: this.createEditor(), + binary: this.createEditor(), + retention: this.createEditor() + }; + readonly form = this.fb.nonNullable.group({ + backup_retention_days: [7, Validators.required], + log_retention_days: [7, Validators.required], + export_cron: '', + binary_cron: '', + retention_cron: '', + enable_auto_export: false, + connection_test_interval_minutes: [0, Validators.min(0)], + global_ssh_key: '', + default_switchos_username: '', + default_switchos_password: '', + pushover_token: '', + pushover_userkey: '', + notify_failures_only: true, + smtp_host: '', + smtp_port: 587, + smtp_login: '', + smtp_password: '', + smtp_notifications_enabled: false, + recipient_email: '', + preferred_language: 'pl' as AppLanguage, + preferred_font: 'default' as AppFont + }); + + constructor() { + effect(() => { + const user = this.auth.user(); + if (!user) { + return; + } + this.form.patchValue( + { + preferred_language: user.preferred_language || 'pl', + preferred_font: user.preferred_font || 'default' + }, + { emitEvent: false } + ); + }); + } + + get scheduleModeOptions() { + return [ + { label: this.ui.instant('settings.scheduleDisabled'), value: 'disabled' }, + { label: this.ui.instant('settings.scheduleDaily'), value: 'daily' }, + { label: this.ui.instant('settings.scheduleWeekly'), value: 'weekly' }, + { label: this.ui.instant('settings.scheduleCustom'), value: 'custom' } + ]; + } + + get weekdayOptions() { + return [ + { label: this.ui.instant('settings.weekdayMonday'), value: '1' }, + { label: this.ui.instant('settings.weekdayTuesday'), value: '2' }, + { label: this.ui.instant('settings.weekdayWednesday'), value: '3' }, + { label: this.ui.instant('settings.weekdayThursday'), value: '4' }, + { label: this.ui.instant('settings.weekdayFriday'), value: '5' }, + { label: this.ui.instant('settings.weekdaySaturday'), value: '6' }, + { label: this.ui.instant('settings.weekdaySunday'), value: '0' } + ]; + } + + readonly languageOptions = APP_LANGUAGE_OPTIONS.map((option) => ({ + label: `${option.flag} ${option.label}`, + value: option.code + })); + + get fontOptions() { + return [ + { label: this.ui.instant('settings.fontDefault'), value: 'default' }, + { label: 'Adwaita Mono', value: 'adwaita_mono' }, + { label: 'Hack', value: 'hack' } + ]; + } + + ngOnInit() { + this.form.controls.preferred_language.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((selectedLanguage) => { + this.previewLanguage(selectedLanguage as AppLanguage | null); + }); + this.reloadSettings(); + this.loadSchedulerStatus(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + save() { + if (this.form.invalid || this.saving) { + return; + } + this.syncEditorsToForm(); + this.saving = true; + + const preferredLanguage = this.form.controls.preferred_language.value as AppLanguage; + const preferredFont = this.form.controls.preferred_font.value as AppFont; + + forkJoin({ + settings: this.api.http.put(`${this.api.baseUrl}/settings`, this.buildPayload()), + preferences: this.auth.updatePreferences({ preferred_language: preferredLanguage, preferred_font: preferredFont }) + }) + .pipe( + finalize(() => { + this.saving = false; + }) + ) + .subscribe({ + next: () => { + this.font.set(preferredFont); + this.ui.success('toast.settingsSaved'); + this.reloadSettings(); + this.loadSchedulerStatus(); + }, + error: () => { + this.ui.error('toast.settingsSaveFailed'); + } + }); + } + + testEmail() { + if (this.testingEmail) { + return; + } + this.testingEmail = true; + this.api.http + .post(`${this.api.baseUrl}/settings/test-email`, {}) + .pipe( + finalize(() => { + this.testingEmail = false; + }) + ) + .subscribe({ + next: () => this.ui.success('toast.testEmailSent'), + error: () => { + this.ui.error('toast.testEmailFailed'); + } + }); + } + + testPushover() { + if (this.testingPushover) { + return; + } + this.testingPushover = true; + this.api.http + .post(`${this.api.baseUrl}/settings/test-pushover`, {}) + .pipe( + finalize(() => { + this.testingPushover = false; + }) + ) + .subscribe({ + next: () => this.ui.success('toast.testPushoverSent'), + error: () => { + this.ui.error('toast.testPushoverFailed'); + } + }); + } + + unlockSshKey() { + if (this.unlockingSshKey) { + return; + } + if (!this.sshRevealPassword.trim()) { + this.ui.error('settings.sshRevealPasswordRequired'); + return; + } + this.unlockingSshKey = true; + this.api.http + .post<{ global_ssh_key: string | null }>(`${this.api.baseUrl}/settings/reveal-ssh-key`, { password: this.sshRevealPassword }) + .pipe( + finalize(() => { + this.unlockingSshKey = false; + }) + ) + .subscribe({ + next: (response) => { + this.form.controls.global_ssh_key.setValue(response.global_ssh_key || ''); + this.sshKeyVisible = true; + this.clearStoredSshKey = false; + this.ui.success('toast.sshKeyUnlocked'); + }, + error: () => { + this.ui.error('settings.sshRevealPasswordInvalid'); + } + }); + } + + hideSshKey() { + if (!this.hasStoredSshKey) { + return; + } + this.sshKeyVisible = false; + this.form.controls.global_ssh_key.setValue(''); + this.sshRevealPassword = ''; + } + + clearSshKey() { + this.clearStoredSshKey = true; + this.hasStoredSshKey = false; + this.sshKeyVisible = true; + this.form.controls.global_ssh_key.setValue(''); + } + + schedulerJob(key: string): SchedulerJobStatus | undefined { + return this.schedulerStatus?.jobs.find((item) => item.key === key); + } + + scheduleSummary(editor: ScheduleEditor): string { + if (editor.mode === 'disabled') { + return this.ui.instant('settings.scheduleDisabledHint'); + } + if (editor.mode === 'daily') { + return this.ui.instant('settings.scheduleDailySummary', { time: `${editor.hour}:${editor.minute}` }); + } + if (editor.mode === 'weekly') { + const weekday = this.weekdayOptions.find((item) => item.value === editor.weekday)?.label || ''; + return this.ui.instant('settings.scheduleWeeklySummary', { weekday, time: `${editor.hour}:${editor.minute}` }); + } + return editor.cron || this.ui.instant('settings.scheduleCustomEmpty'); + } + + connectionTestSummary(): string { + const minutes = Number(this.form.controls.connection_test_interval_minutes.value || 0); + return minutes > 0 ? this.ui.instant('settings.connectionTestsEverySummary', { minutes }) : this.ui.instant('settings.connectionTestsDisabledHint'); + } + + scheduleEnabled(editor: ScheduleEditor): boolean { + return editor.mode !== 'disabled'; + } + + scheduleSeverity(editor: ScheduleEditor): 'success' | 'secondary' { + return this.scheduleEnabled(editor) ? 'success' : 'secondary'; + } + + connectionTestSeverity(): 'success' | 'secondary' { + return Number(this.form.controls.connection_test_interval_minutes.value || 0) > 0 ? 'success' : 'secondary'; + } + + setBooleanSetting(controlName: BooleanSettingControl, value: boolean) { + this.form.controls[controlName].setValue(value); + this.form.controls[controlName].markAsDirty(); + } + + normalizeTime(editor: ScheduleEditor) { + editor.hour = this.padTime(editor.hour, 23); + editor.minute = this.padTime(editor.minute, 59); + } + + previewFont() { + this.font.set(this.form.controls.preferred_font.value as AppFont); + } + + previewLanguage(selectedLanguage?: AppLanguage | null) { + const nextLanguage = (selectedLanguage || this.form.controls.preferred_language.value || 'pl') as AppLanguage; + this.form.controls.preferred_language.setValue(nextLanguage, { emitEvent: false }); + if (this.auth.user()) { + this.language.setForAuthenticatedUser(nextLanguage); + return; + } + this.language.set(nextLanguage); + } + + private createEditor(): ScheduleEditor { + return { mode: 'disabled', hour: '02', minute: '00', weekday: '1', cron: '' }; + } + + private reloadSettings() { + this.api.http.get(`${this.api.baseUrl}/settings`).subscribe((response) => { + this.hasStoredSshKey = response.has_global_ssh_key; + this.sshKeyVisible = !response.has_global_ssh_key; + this.clearStoredSshKey = false; + this.sshRevealPassword = ''; + const user = this.auth.user(); + this.form.patchValue({ + backup_retention_days: response.backup_retention_days, + log_retention_days: response.log_retention_days, + export_cron: response.export_cron || '', + binary_cron: response.binary_cron || '', + retention_cron: response.retention_cron || '', + enable_auto_export: response.enable_auto_export, + connection_test_interval_minutes: Number(response.connection_test_interval_minutes || 0), + global_ssh_key: '', + default_switchos_username: response.default_switchos_username || '', + default_switchos_password: response.default_switchos_password || '', + pushover_token: response.pushover_token || '', + pushover_userkey: response.pushover_userkey || '', + notify_failures_only: response.notify_failures_only, + smtp_host: response.smtp_host || '', + smtp_port: Number(response.smtp_port || 587), + smtp_login: response.smtp_login || '', + smtp_password: response.smtp_password || '', + smtp_notifications_enabled: response.smtp_notifications_enabled, + recipient_email: response.recipient_email || '', + preferred_language: (user?.preferred_language || this.language.current() || 'pl') as AppLanguage, + preferred_font: (user?.preferred_font || 'default') as AppFont + }, { emitEvent: false }); + this.hydrateEditors(); + }); + } + + private buildPayload() { + const raw = this.form.getRawValue(); + const normalizedKey = raw.global_ssh_key.trim(); + return { + backup_retention_days: Number(raw.backup_retention_days || 0), + log_retention_days: Number(raw.log_retention_days || 0), + export_cron: (raw.export_cron || '').trim(), + binary_cron: (raw.binary_cron || '').trim(), + retention_cron: (raw.retention_cron || '').trim(), + enable_auto_export: Boolean(raw.enable_auto_export), + connection_test_interval_minutes: Number(raw.connection_test_interval_minutes || 0), + global_ssh_key: normalizedKey || null, + default_switchos_username: this.normalizeOptionalText(raw.default_switchos_username), + default_switchos_password: this.normalizeOptionalText(raw.default_switchos_password), + pushover_token: this.normalizeOptionalText(raw.pushover_token), + pushover_userkey: this.normalizeOptionalText(raw.pushover_userkey), + notify_failures_only: Boolean(raw.notify_failures_only), + smtp_host: this.normalizeOptionalText(raw.smtp_host), + smtp_port: Number(raw.smtp_port || 587), + smtp_login: this.normalizeOptionalText(raw.smtp_login), + smtp_password: this.normalizeOptionalText(raw.smtp_password), + smtp_notifications_enabled: Boolean(raw.smtp_notifications_enabled), + recipient_email: this.normalizeOptionalText(raw.recipient_email), + clear_global_ssh_key: this.clearStoredSshKey + }; + } + + private hydrateEditors() { + const exportEditor = this.editorFromCron(this.form.controls.export_cron.value); + if (!this.form.controls.enable_auto_export.value) { + exportEditor.mode = 'disabled'; + } + this.scheduleEditors.export = exportEditor; + this.scheduleEditors.binary = this.editorFromCron(this.form.controls.binary_cron.value); + this.scheduleEditors.retention = this.editorFromCron(this.form.controls.retention_cron.value); + } + + private syncEditorsToForm() { + this.normalizeTime(this.scheduleEditors.export); + this.normalizeTime(this.scheduleEditors.binary); + this.normalizeTime(this.scheduleEditors.retention); + this.form.patchValue({ + export_cron: this.editorToCron(this.scheduleEditors.export), + binary_cron: this.editorToCron(this.scheduleEditors.binary), + retention_cron: this.editorToCron(this.scheduleEditors.retention), + enable_auto_export: this.scheduleEditors.export.mode !== 'disabled' + }); + } + + private editorFromCron(cron: string): ScheduleEditor { + const normalized = (cron || '').trim(); + if (!normalized) { + return this.createEditor(); + } + + const parts = normalized.split(/\s+/); + if (parts.length === 5) { + const [minute, hour, day, month, dayOfWeek] = parts; + if (day === '*' && month === '*' && dayOfWeek === '*') { + return { mode: 'daily', hour: this.padTime(hour, 23), minute: this.padTime(minute, 59), weekday: '1', cron: normalized }; + } + if (day === '*' && month === '*' && /^[0-7]$/.test(dayOfWeek)) { + return { mode: 'weekly', hour: this.padTime(hour, 23), minute: this.padTime(minute, 59), weekday: dayOfWeek, cron: normalized }; + } + } + + return { mode: 'custom', hour: '02', minute: '00', weekday: '1', cron: normalized }; + } + + private editorToCron(editor: ScheduleEditor): string { + if (editor.mode === 'disabled') { + return ''; + } + if (editor.mode === 'daily') { + return `${editor.minute} ${editor.hour} * * *`; + } + if (editor.mode === 'weekly') { + return `${editor.minute} ${editor.hour} * * ${editor.weekday}`; + } + return editor.cron.trim(); + } + + private padTime(value: string, max: number): string { + const numeric = Math.min(max, Math.max(0, Number(value || 0))); + return String(Math.floor(numeric)).padStart(2, '0'); + } + + private normalizeOptionalText(value: string | null | undefined): string | null { + const normalized = (value || '').trim(); + return normalized || null; + } + + private loadSchedulerStatus() { + this.api.http.get(`${this.api.baseUrl}/settings/scheduler-status`).subscribe((status) => { + this.schedulerStatus = status; + }); + } +} diff --git a/frontend/src/app/features/swos-beta/swos-beta-page.component.html b/frontend/src/app/features/swos-beta/swos-beta-page.component.html new file mode 100644 index 0000000..0e4ab66 --- /dev/null +++ b/frontend/src/app/features/swos-beta/swos-beta-page.component.html @@ -0,0 +1,80 @@ + +
+ +
+
+ + +
+
+ {{ 'switchosBeta.warningHeadline' | translate }} +

{{ 'switchosBeta.warningBody' | translate }}

+
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + +
{{ 'switchosBeta.resultEmpty' | translate }}
+ +
+
+ {{ 'switchosBeta.baseUrl' | translate }} + {{ probeResult.base_url }} +
+
+ {{ 'switchosBeta.httpStatus' | translate }} + {{ probeResult.status_code }} +
+
+ {{ 'switchosBeta.authMode' | translate }} + {{ probeResult.auth_mode }} +
+
+ {{ 'switchosBeta.pageTitle' | translate }} + {{ probeResult.page_title || '—' }} +
+
+ {{ 'switchosBeta.serverHeader' | translate }} + {{ probeResult.server || '—' }} +
+
+ {{ 'switchosBeta.backupEndpoint' | translate }} + {{ (probeResult.backup_endpoint_ok ? 'switchosBeta.available' : 'switchosBeta.unavailable') | translate }} +
+
{{ probeResult.note }}
+
+ +
{{ lastError }}
+
+
diff --git a/frontend/src/app/features/swos-beta/swos-beta-page.component.ts b/frontend/src/app/features/swos-beta/swos-beta-page.component.ts new file mode 100644 index 0000000..97b9436 --- /dev/null +++ b/frontend/src/app/features/swos-beta/swos-beta-page.component.ts @@ -0,0 +1,131 @@ +import { CommonModule } from '@angular/common'; +import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; +import { Component, inject } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { finalize } from 'rxjs'; +import { ButtonModule } from 'primeng/button'; +import { InputTextModule } from 'primeng/inputtext'; +import { TagModule } from 'primeng/tag'; + +import { ApiService } from '../../core/services/api.service'; +import { UiService } from '../../core/services/ui.service'; +import { PageHeaderComponent } from '../../shared/ui/page-header.component'; +import { SectionCardComponent } from '../../shared/ui/section-card.component'; + +interface SwosBetaProbeResult { + success: boolean; + base_url: string; + status_code: number; + auth_mode: string; + page_title?: string | null; + content_type?: string | null; + server?: string | null; + save_backup_visible: boolean; + backup_endpoint_ok: boolean; + note?: string | null; +} + +@Component({ + standalone: true, + imports: [CommonModule, ReactiveFormsModule, TranslateModule, ButtonModule, InputTextModule, TagModule, PageHeaderComponent, SectionCardComponent], + templateUrl: './swos-beta-page.component.html' +}) +export class SwosBetaPageComponent { + private readonly api = inject(ApiService); + private readonly fb = inject(FormBuilder); + private readonly ui = inject(UiService); + + probing = false; + downloading = false; + lastError = ''; + probeResult?: SwosBetaProbeResult; + + readonly form = this.fb.nonNullable.group({ + label: '', + host: ['', Validators.required], + port: [80, [Validators.required, Validators.min(1), Validators.max(65535)]], + username: ['admin', Validators.required], + password: '' + }); + + get formValue() { + return this.form.getRawValue(); + } + + probe() { + if (this.form.invalid || this.probing) { + this.form.markAllAsTouched(); + return; + } + + this.lastError = ''; + this.probing = true; + this.api.http + .post(`${this.api.baseUrl}/swos-beta/probe`, this.formValue) + .pipe(finalize(() => (this.probing = false))) + .subscribe({ + next: (result) => { + this.probeResult = result; + this.ui.success('toast.swosBetaProbeOk'); + }, + error: (error: HttpErrorResponse) => { + this.probeResult = undefined; + this.lastError = this.extractError(error); + this.ui.error('toast.swosBetaProbeFailed'); + } + }); + } + + download() { + if (this.form.invalid || this.downloading) { + this.form.markAllAsTouched(); + return; + } + + this.lastError = ''; + this.downloading = true; + this.api.http + .post(`${this.api.baseUrl}/swos-beta/download`, this.formValue, { + observe: 'response', + responseType: 'blob' + }) + .pipe(finalize(() => (this.downloading = false))) + .subscribe({ + next: (response) => { + this.saveBlob(response); + this.ui.success('toast.swosBetaDownloadOk'); + }, + error: (error: HttpErrorResponse) => { + this.lastError = this.extractError(error); + this.ui.error('toast.swosBetaDownloadFailed'); + } + }); + } + + private saveBlob(response: HttpResponse) { + const blob = response.body || new Blob(); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = this.extractFilename(response.headers.get('content-disposition')); + link.click(); + URL.revokeObjectURL(url); + } + + private extractFilename(disposition: string | null): string { + const match = disposition?.match(/filename="?([^\"]+)"?/i); + return match?.[1] || 'switchos-backup.swb'; + } + + private extractError(error: HttpErrorResponse): string { + const detail = error.error?.detail; + if (typeof detail === 'string' && detail.trim()) { + return detail.trim(); + } + if (typeof error.error === 'string' && error.error.trim()) { + return error.error.trim(); + } + return this.ui.instant('switchosBeta.genericError'); + } +} diff --git a/frontend/src/app/shared/auth/auth-toolbar.component.html b/frontend/src/app/shared/auth/auth-toolbar.component.html new file mode 100644 index 0000000..e1c311a --- /dev/null +++ b/frontend/src/app/shared/auth/auth-toolbar.component.html @@ -0,0 +1,22 @@ +
+ + + +
diff --git a/frontend/src/app/shared/auth/auth-toolbar.component.ts b/frontend/src/app/shared/auth/auth-toolbar.component.ts new file mode 100644 index 0000000..0a4965d --- /dev/null +++ b/frontend/src/app/shared/auth/auth-toolbar.component.ts @@ -0,0 +1,28 @@ +import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; + +import { AppLanguage, AppLanguageOption, LanguageService } from '../../core/services/language.service'; +import { ThemeService } from '../../core/services/theme.service'; + +@Component({ + selector: 'app-auth-toolbar', + standalone: true, + imports: [CommonModule, FormsModule, ButtonModule], + templateUrl: './auth-toolbar.component.html' +}) +export class AuthToolbarComponent { + readonly theme = inject(ThemeService); + readonly language = inject(LanguageService); + + readonly languageOptions = this.language.options; + + trackByLanguageCode(_: number, option: AppLanguageOption) { + return option.code; + } + + changeLanguage(lang: AppLanguage | string) { + this.language.set(lang as AppLanguage); + } +} diff --git a/frontend/src/app/shared/layout/app-sidebar.component.html b/frontend/src/app/shared/layout/app-sidebar.component.html new file mode 100644 index 0000000..bb9f9ef --- /dev/null +++ b/frontend/src/app/shared/layout/app-sidebar.component.html @@ -0,0 +1,27 @@ + + + + + diff --git a/frontend/src/app/shared/layout/app-sidebar.component.ts b/frontend/src/app/shared/layout/app-sidebar.component.ts new file mode 100644 index 0000000..a460361 --- /dev/null +++ b/frontend/src/app/shared/layout/app-sidebar.component.ts @@ -0,0 +1,16 @@ +import { CommonModule } from '@angular/common'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { RouterLink, RouterLinkActive } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; + +@Component({ + selector: 'app-sidebar', + standalone: true, + imports: [CommonModule, RouterLink, RouterLinkActive, TranslateModule], + templateUrl: './app-sidebar.component.html' +}) +export class AppSidebarComponent { + @Input() collapsed = false; + @Input() items: Array<{ label: string; link: string; icon: string; exact?: boolean }> = []; + @Output() navigate = new EventEmitter(); +} diff --git a/frontend/src/app/shared/layout/app-topbar.component.html b/frontend/src/app/shared/layout/app-topbar.component.html new file mode 100644 index 0000000..5895b0b --- /dev/null +++ b/frontend/src/app/shared/layout/app-topbar.component.html @@ -0,0 +1,42 @@ +
+
+ +
+
{{ 'topbar.caption' | translate }}
+
{{ pageTitle | translate }}
+
+
+ +
+ + + + +
+ +
+ {{ username }} + {{ 'topbar.role' | translate }} +
+
+ + +
+
diff --git a/frontend/src/app/shared/layout/app-topbar.component.ts b/frontend/src/app/shared/layout/app-topbar.component.ts new file mode 100644 index 0000000..05984a8 --- /dev/null +++ b/frontend/src/app/shared/layout/app-topbar.component.ts @@ -0,0 +1,42 @@ +import { CommonModule } from '@angular/common'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { AvatarModule } from 'primeng/avatar'; +import { ButtonModule } from 'primeng/button'; + +export interface TopbarLanguageOption { + code: string; + label: string; + flag: string; +} + +@Component({ + selector: 'app-topbar', + standalone: true, + imports: [CommonModule, FormsModule, TranslateModule, ButtonModule, AvatarModule], + templateUrl: './app-topbar.component.html' +}) +export class AppTopbarComponent { + @Input() pageTitle = 'dashboard.title'; + @Input() username = 'admin'; + @Input() lang = 'pl'; + @Input() themeMode: 'light' | 'dark' = 'light'; + @Input() languages: TopbarLanguageOption[] = []; + @Output() menuClick = new EventEmitter(); + @Output() themeClick = new EventEmitter(); + @Output() languageChange = new EventEmitter(); + @Output() logoutClick = new EventEmitter(); + + get userInitials(): string { + return this.username.slice(0, 2).toUpperCase(); + } + + trackByLanguageCode(_: number, option: TopbarLanguageOption) { + return option.code; + } + + onLanguageSelect(value: string) { + this.languageChange.emit(value); + } +} diff --git a/frontend/src/app/shared/ui/page-header.component.html b/frontend/src/app/shared/ui/page-header.component.html new file mode 100644 index 0000000..675eaa3 --- /dev/null +++ b/frontend/src/app/shared/ui/page-header.component.html @@ -0,0 +1,10 @@ + diff --git a/frontend/src/app/shared/ui/page-header.component.ts b/frontend/src/app/shared/ui/page-header.component.ts new file mode 100644 index 0000000..9336498 --- /dev/null +++ b/frontend/src/app/shared/ui/page-header.component.ts @@ -0,0 +1,14 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-page-header', + standalone: true, + imports: [CommonModule], + templateUrl: './page-header.component.html' +}) +export class PageHeaderComponent { + @Input({ required: true }) title = ''; + @Input() subtitle = ''; + @Input() eyebrow = ''; +} diff --git a/frontend/src/app/shared/ui/section-card.component.html b/frontend/src/app/shared/ui/section-card.component.html new file mode 100644 index 0000000..96d8268 --- /dev/null +++ b/frontend/src/app/shared/ui/section-card.component.html @@ -0,0 +1,12 @@ + +
+
+

{{ title }}

+

{{ subtitle }}

+
+
+ +
+
+ +
diff --git a/frontend/src/app/shared/ui/section-card.component.ts b/frontend/src/app/shared/ui/section-card.component.ts new file mode 100644 index 0000000..642293c --- /dev/null +++ b/frontend/src/app/shared/ui/section-card.component.ts @@ -0,0 +1,14 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { CardModule } from 'primeng/card'; + +@Component({ + selector: 'app-section-card', + standalone: true, + imports: [CommonModule, CardModule], + templateUrl: './section-card.component.html' +}) +export class SectionCardComponent { + @Input() title = ''; + @Input() subtitle = ''; +} diff --git a/frontend/src/app/shared/ui/stat-card.component.html b/frontend/src/app/shared/ui/stat-card.component.html new file mode 100644 index 0000000..ae11e7c --- /dev/null +++ b/frontend/src/app/shared/ui/stat-card.component.html @@ -0,0 +1,13 @@ + +
+
+
{{ label }}
+
{{ value }}
+
{{ hint }}
+
+
+ +
+
+ +
diff --git a/frontend/src/app/shared/ui/stat-card.component.ts b/frontend/src/app/shared/ui/stat-card.component.ts new file mode 100644 index 0000000..1b772e1 --- /dev/null +++ b/frontend/src/app/shared/ui/stat-card.component.ts @@ -0,0 +1,24 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { CardModule } from 'primeng/card'; +import { TagModule } from 'primeng/tag'; + +@Component({ + selector: 'app-stat-card', + standalone: true, + imports: [CommonModule, CardModule, TagModule], + templateUrl: './stat-card.component.html' +}) +export class StatCardComponent { + @Input({ required: true }) label = ''; + @Input({ required: true }) value: string | number = ''; + @Input() hint = ''; + @Input() tag = ''; + @Input() icon = 'pi pi-chart-bar'; + @Input() iconClass = ''; + @Input() severity: 'success' | 'info' | 'warning' | 'warn' | 'danger' | 'secondary' | 'contrast' | undefined = 'info'; + + get tagSeverity(): 'success' | 'secondary' | 'info' | 'warn' | 'danger' | 'contrast' | null | undefined { + return this.severity === 'warning' ? 'warn' : this.severity; + } +} diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json new file mode 100644 index 0000000..576fa8e --- /dev/null +++ b/frontend/src/assets/i18n/en.json @@ -0,0 +1,541 @@ +{ + "app": { + "menu": "Menu" + }, + "sidebar": { + "title": "Mikrotik Backup System", + "subtitle": "Device backup platform" + }, + "topbar": { + "caption": "mikrotik / control center", + "role": "administrator", + "languageSelector": "Language selector" + }, + "common": { + "apply": "Apply", + "reset": "Reset", + "delete": "Delete", + "confirm": "Confirm", + "cancel": "Cancel", + "download": "Download", + "email": "Send e-mail", + "preview": "Preview", + "restore": "Restore", + "actions": "Actions", + "open": "Open", + "edit": "Edit", + "diff": "Diff", + "ok": "OK", + "idle": "Idle", + "asc": "Ascending", + "desc": "Descending", + "enabled": "Enabled", + "disabled": "Disabled", + "failed": "Failed" + }, + "nav": { + "dashboard": "Dashboard", + "routers": "Devices", + "files": "Repository", + "settings": "Settings", + "logs": "Logs", + "logout": "Logout", + "theme": "Theme", + "changePassword": "Change password", + "diffConfigs": "Config diff", + "switchosBeta": "SwitchOS beta" + }, + "auth": { + "username": "Username", + "password": "Password", + "login": "Login", + "register": "Register", + "confirmPassword": "Confirm password", + "changePassword": "Change password", + "currentPassword": "Current password", + "newPassword": "New password", + "backToLogin": "Back to login", + "backToApp": "Back to app", + "loginSubtitle": "Sign in to continue.", + "loginFailed": "Login failed", + "accountCreated": "Account created", + "registrationFailed": "Registration failed", + "passwordsMismatch": "Passwords do not match", + "changePasswordFailed": "Password change failed", + "securityEyebrow": "account / security", + "changePasswordSubtitle": "Update the administrator password without extra visual clutter.", + "changePasswordCardSubtitle": "Enter the current password and set new credentials.", + "passwordPanelSubtitle": "Quick check for password strength and field consistency before saving.", + "passwordStrength": "Password strength", + "passwordWeak": "Weak", + "passwordMedium": "Medium", + "passwordStrong": "Strong", + "ruleLength": "At least 8 characters", + "ruleDigit": "At least one digit", + "ruleMatch": "Both fields match", + "passwordsMatchHint": "The new password and confirmation match." + }, + "dashboard": { + "title": "Dashboard", + "eyebrow": "home / dashboard", + "subtitle": "Overview of backups, exports and operational activity in one place.", + "exportAll": "Export all", + "binaryAll": "Binary backup", + "managedRouters": "Devices", + "managedRoutersHint": "All managed devices", + "inventoryTag": "Fleet", + "exportsCard": "Exports", + "exportsHint": "Readable configuration snapshots", + "textTag": "Text", + "binaryCard": "Binary backups", + "binaryHint": "Recovery points", + "binaryTag": "Binary", + "allFilesCard": "All files", + "allFilesHint": "Artifacts in the repository", + "archiveTag": "Archive", + "storageTitle": "Storage utilization", + "storageSubtitle": "Current overview of repository usage and free space.", + "folderUsage": "Folder usage", + "diskUsage": "Disk usage", + "totalDisk": "Total disk", + "freeSpace": "Free space", + "activityTitle": "Recent activity", + "activitySubtitle": "Latest operational events from the backend.", + "noActivity": "No recent events to display.", + "avgBackupsPerRouter": "Avg backups / router", + "activitySuccess": "Completed task", + "activityFailure": "Needs attention", + "activityMaintenance": "Maintenance", + "activityDelivery": "Delivery", + "operationsTitle": "Operations center", + "operationsSubtitle": "Primary actions and live repository indicators in one place.", + "latestSnapshot": "Latest snapshot", + "coverageLabel": "Fleet coverage", + "coverageHint": "Routers with at least one backup", + "weeklyActivityLabel": "7-day activity", + "weeklyActivityHint": "New backups created this week", + "busiestRouterLabel": "Busiest router", + "routerSnapshotsHint": "{{count}} snapshots in the repository", + "exportShareLabel": "Export share", + "activityTodayLabel": "Events today", + "noneLabel": "None", + "activityTodayHint": "Entries created today", + "usedSpace": "Used space", + "storageViewCapacity": "Capacity", + "storageViewCapacityHint": "Disk, repository usage and free space shown on one scale.", + "storageViewMix": "Backup types", + "storageViewMixHint": "Split of all snapshots into text exports and binary backups.", + "storageViewActivity": "7-day activity", + "storageViewActivityHint": "Number of new backups created during the last seven days.", + "storageViewRouters": "Top devices", + "storageViewRoutersHint": "Devices with the highest number of snapshots in the repository.", + "storageChartEmpty": "There is not enough data to draw this chart yet.", + "storageSnapshotTitle": "Repository metrics", + "storageSnapshotHint": "Quick snapshot of the most important storage and backup indicators." + }, + "routers": { + "title": "Devices", + "detailTitle": "Device details", + "add": "Add device", + "eyebrow": "device inventory", + "subtitle": "Manage RouterOS and SwitchOS devices plus their backups.", + "registeredDevices": "Registered devices", + "fleetTag": "Fleet", + "sshPassword": "Password", + "passwordHint": "Password-based access", + "credsTag": "Creds", + "sshKey": "SSH key", + "keyHint": "Key-based access", + "securityTag": "Security", + "defaultPort": "Port 22", + "defaultPortHint": "Standard SSH endpoints", + "portTag": "Port", + "listTitle": "Device list", + "listSubtitle": "Unified view for RouterOS and SwitchOS devices.", + "name": "Name", + "endpoint": "Endpoint", + "access": "Access", + "routerOsTarget": "RouterOS target", + "passwordMode": "Password", + "noPassword": "No password", + "keyMode": "Key", + "globalKeyMode": "Global key", + "noKey": "No key", + "createDialogTitle": "Add device", + "editDialogTitle": "Edit device", + "host": "Host", + "port": "Port", + "sshUser": "Username", + "sshPrivateKey": "SSH private key", + "optionalPassword": "Optional password", + "optionalPrivateKey": "Optional private key", + "saveRouter": "Save device", + "profileEyebrow": "router profile", + "detailSubtitle": "Device operations and backup history", + "exportOne": "Export", + "binaryOne": "Binary", + "testConnection": "Test connection", + "deleteRouter": "Delete router", + "exportsLabel": "Exports", + "exportsLabelHint": "Text snapshots", + "binaryLabel": "Binary backups", + "binaryLabelHint": "Recovery images", + "connectionLabel": "Connection", + "connectionLabelHint": "Status from the latest automatic or manual connection test", + "probeTag": "Probe", + "accessTag": "Access", + "sshUserHint": "Effective device login", + "deviceStatusTitle": "Device status", + "deviceStatusSubtitle": "Stored metadata from the latest automatic or manual connection test.", + "hostname": "Hostname", + "model": "Model", + "version": "Version", + "uptime": "Uptime", + "noConnection": "No saved connection test yet. Run a manual test or enable automatic checks in settings.", + "previewTitle": "Export preview", + "previewSubtitle": "Most recently opened export file.", + "noPreview": "Select an export file to preview its contents.", + "diffTitle": "Latest diff", + "diffSubtitle": "Difference against the newest export.", + "exportsTableTitle": "Exports", + "exportsTableSubtitle": "Readable RouterOS snapshots.", + "binaryTableTitle": "Binary backups", + "binaryTableSubtitle": "Binary files and SwitchOS backups.", + "summaryKeyAccess": "with key-based access", + "summaryPasswordAccess": "with password access", + "connectionStateTitle": "Connection state", + "lastTestAt": "Last test", + "lastError": "Last error", + "deviceStatusManualHint": "Automatic checks use the interval from settings. The manual test button is still available.", + "previewModalHint": "The last opened export is available in a modal.", + "openPreviewModal": "Open preview", + "diffModalHint": "The last loaded diff is available in a modal.", + "openDiffModal": "Open diff", + "noDiff": "Choose an export and run a diff to see the latest comparison.", + "routeros": "RouterOS", + "switchos": "SwitchOS", + "deviceType": "Device type", + "defaultCredentials": "Default credentials", + "localCredentials": "Local credentials", + "noCredentials": "No credentials", + "switchUserPlaceholder": "Empty = use settings default", + "switchPasswordPlaceholder": "Empty = use settings default", + "switchDefaultsHint": "For SwitchOS you can leave username and password empty to use the defaults from settings.", + "downloadSwitchBackup": "Download backup", + "httpStatus": "HTTP status", + "serverHeader": "Server header", + "authMode": "Auth mode", + "backupEndpoint": "Backup endpoint", + "backupAvailable": "Available", + "backupUnavailable": "Unavailable", + "connectionSectionTitle": "Connection profile", + "connectionSectionHint": "Basic device identity and endpoint used to reach it.", + "credentialsSectionTitle": "Access and credentials", + "routerDialogSubtitle": "Set the device endpoint, SSH access data and your preferred login method.", + "switchDialogSubtitle": "Set the SwitchOS endpoint and optional local or shared credentials from settings." + }, + "files": { + "title": "Repository", + "eyebrow": "artifact repository", + "subtitle": "Search, compare and deliver backups from one clear view.", + "downloadZip": "Download ZIP", + "visibleFiles": "Visible files", + "visibleFilesHint": "Current filter result", + "liveTag": "Live", + "selected": "Selected", + "selectedHint": "Ready for bulk actions", + "batchTag": "Batch", + "exportsCard": "Exports", + "exportsHint": "Configuration snapshots", + "binaryCard": "Binary backups", + "binaryHint": "Recovery images", + "filtersTitle": "Filters", + "filtersSubtitle": "Refine the list by router, type or keyword.", + "searchLabel": "Search", + "searchPlaceholder": "Search by file or router", + "typeLabel": "Type", + "routerLabel": "Device", + "dateLabel": "Date", + "datePlaceholder": "Pick a date", + "sortLabel": "Sort by", + "orderLabel": "Order", + "allTypes": "All types", + "allRouters": "All devices", + "sortNewest": "Newest", + "sortName": "Name", + "sortRouter": "Device", + "sortType": "Type", + "tableTitle": "Repository table", + "tableSubtitle": "Artifacts available for download, e-mail and restore.", + "compareHint": "Select exactly two .rsc files to compare them.", + "compareSelected": "Compare selected exports", + "fileColumn": "File", + "typeColumn": "Type", + "routerColumn": "Device", + "createdColumn": "Created", + "actionsColumn": "Actions", + "checksum": "Checksum", + "exportType": "Export", + "binaryType": "Binary backup", + "previewDialogTitle": "Export preview", + "diffDialogTitle": "Export diff", + "openHtmlDiff": "Open HTML diff", + "sizeColumn": "Size", + "compareColumn": "Compare", + "compareOlder": "Older file", + "compareNewer": "Newer file", + "pickOlder": "Pick older backup", + "pickNewer": "Pick newer backup", + "compareLatestPair": "Latest pair", + "setOlder": "Set as older", + "setNewer": "Set as newer", + "latestForRouter": "Router diff", + "binaryNoCompare": "Diff available for .rsc only", + "openPlainDiff": "Show plain diff", + "minutesAgo": "{{value}} min ago", + "hoursAgo": "{{value}} h ago", + "daysAgo": "{{value}} d ago", + "compareTitle": "Export comparison", + "compareSubtitle": "Pick two .rsc files and launch the diff without digging through the whole table.", + "exportPoolLabel": "exports ready to compare", + "compareSelectionHint": "Pick an older and a newer file", + "compareReadySameRouter": "Pair ready · device {{router}}", + "compareReadyMixedRouters": "Pair ready · mixed devices" + }, + "settings": { + "title": "Settings", + "eyebrow": "platform configuration", + "subtitle": "Control schedules, retention, notifications, connection tests and shared SSH data.", + "testEmail": "Test e-mail", + "testPushover": "Test Pushover", + "retentionTitle": "Retention", + "retentionSubtitle": "Automatic cleanup windows for files and logs.", + "backupRetentionDays": "Backup retention days", + "logRetentionDays": "Log retention days", + "retentionCron": "Retention cron", + "automationTitle": "Automation", + "automationSubtitle": "Schedules for export, binary jobs, retention and connection checks.", + "enableAutoExport": "Enable auto export", + "enableAutoExportHint": "Run export jobs using the cron rules below.", + "exportCron": "Export cron", + "binaryCron": "Binary cron", + "notificationsTitle": "Notifications", + "notificationsSubtitle": "SMTP and Pushover delivery configuration.", + "smtpEnabled": "SMTP enabled", + "smtpEnabledHint": "Send notifications through the SMTP gateway.", + "failuresOnly": "Failures only", + "failuresOnlyHint": "Limit alerts to failed jobs.", + "smtpHost": "SMTP host", + "smtpPort": "SMTP port", + "smtpLogin": "SMTP login", + "smtpPassword": "SMTP password", + "recipientEmail": "Recipient e-mail", + "pushoverToken": "Pushover token", + "pushoverUserKey": "Pushover user key", + "pushoverTokenPlaceholder": "Application token", + "pushoverUserKeyPlaceholder": "User key", + "sshDefaultsTitle": "Default Credentials", + "sshDefaultsSubtitle": "Shared SSH key and default SwitchOS login used across managed devices.", + "globalSshPrivateKey": "Global SSH private key", + "globalSshPrivateKeyPlaceholder": "Paste PEM or OpenSSH private key", + "save": "Save settings", + "scheduleDisabled": "Disabled", + "scheduleDaily": "Daily", + "scheduleWeekly": "Weekly", + "scheduleCustom": "Custom cron", + "scheduleMode": "Schedule mode", + "scheduleTime": "Time", + "scheduleWeekday": "Weekday", + "weekdayMonday": "Monday", + "weekdayTuesday": "Tuesday", + "weekdayWednesday": "Wednesday", + "weekdayThursday": "Thursday", + "weekdayFriday": "Friday", + "weekdaySaturday": "Saturday", + "weekdaySunday": "Sunday", + "scheduleDisabledHint": "The job will not run automatically.", + "scheduleDailySummary": "Every day at {{time}}", + "scheduleWeeklySummary": "Every {{weekday}} at {{time}}", + "scheduleCustomEmpty": "Enter a custom cron expression", + "statusEnabled": "Enabled", + "statusDisabled": "Disabled", + "noNextRun": "No next run scheduled", + "exportScheduleTitle": "Text exports", + "binaryScheduleTitle": "Binary backups", + "automationPlannerTitle": "Job planner", + "automationPlannerSubtitle": "Every job has its own schedule, so export, binary backup and retention can run in separate windows.", + "automationPlannerTag": "Flexible windows", + "exportPlannerHint": "Decide when readable text exports should be created. Disabled mode stops the automation completely.", + "binaryPlannerHint": "Separate window for full binary backups when you need restore points.", + "retentionPlannerHint": "Retention cleans old backups and logs on its own schedule.", + "connectionTestsTitle": "Automatic connection tests", + "connectionTestsHint": "The application can refresh router status automatically. Set 0 to disable automatic tests.", + "connectionTestIntervalMinutes": "Check every X minutes", + "connectionTestsEverySummary": "Every {{minutes}} minutes", + "connectionTestsDisabledHint": "Automatic connection tests are disabled.", + "sshKeyHelper": "Keep the shared SSH key on the right side. Reveal it only after confirming your account password.", + "sshKeyStoredTag": "Stored key", + "sshKeyWillBeRemovedTag": "Will be removed", + "sshRevealHint": "The current key stays hidden until you confirm your password. You can still paste a new key below to replace it.", + "revealSshPassword": "Current account password", + "revealSshPasswordPlaceholder": "Enter password to reveal the key", + "revealSshKey": "Reveal key", + "hideSshKey": "Hide key", + "clearSshKey": "Clear key", + "sshKeyClearNotice": "The stored shared SSH key will be removed after saving.", + "globalSshPrivateKeyHiddenPlaceholder": "Stored key hidden. Enter the password above to reveal it, or paste a new key here to replace it.", + "sshRevealPasswordRequired": "Enter your current password to reveal the SSH key.", + "sshRevealPasswordInvalid": "The password used to reveal the SSH key is invalid.", + "schedulerAutoExportLabel": "Automatic exports", + "schedulerBinaryLabel": "Binary backups", + "schedulerRetentionLabel": "Retention cleanup", + "schedulerConnectionLabel": "Connection checks", + "schedulerLogsLabel": "Log cleanup", + "schedulerLogsDescription": "Every 24 hours", + "schedulerCronDescription": "{{description}}", + "schedulerInvalidCron": "Invalid cron expression", + "interfaceTitle": "Interface configuration", + "interfaceSubtitle": "Language and typography preferences saved for your account.", + "interfacePreferencesTitle": "Workspace appearance", + "interfacePreferencesHint": "Choose the default language and font family for the whole application.", + "interfacePreferencesTag": "Per-user", + "fontFamily": "Font family", + "fontDefault": "Default", + "switchosDefaultsTitle": "Default SwitchOS credentials", + "switchosDefaultsHint": "Used when a SwitchOS device has no local username or password.", + "defaultSwitchosUsername": "Default SwitchOS username", + "defaultSwitchosPassword": "Default SwitchOS password" + }, + "logs": { + "title": "Logs", + "eyebrow": "operational history", + "subtitle": "Audit the latest export, restore and maintenance events.", + "daysPlaceholder": "days", + "deleteOlderThan": "Delete older than", + "entriesLabel": "Entries", + "entriesHint": "Loaded rows", + "auditTag": "Audit", + "retentionLabel": "Retention", + "retentionHint": "Cleanup threshold", + "policyTag": "Policy", + "daysSuffix": "days", + "tableTitle": "Log table", + "tableSubtitle": "Chronological list of operations captured by the backend.", + "timestampColumn": "Timestamp", + "messageColumn": "Message", + "retentionInfoLabel": "Configured log retention" + }, + "toast": { + "success": "Done", + "info": "Info", + "error": "Error", + "exportPreviewLoaded": "Export preview loaded.", + "backupSentEmail": "Backup sent by e-mail.", + "binaryUploaded": "Binary backup uploaded to the router.", + "backupDeleted": "Backup deleted.", + "selectedBackupsDeleted": "Selected backups deleted.", + "diffLoaded": "Diff loaded.", + "archivePrepared": "Archive prepared.", + "exportedRouters": "Export completed for {{count}} devices.", + "binaryCompletedRouters": "Binary backup completed for {{count}} devices.", + "routerCreated": "Router created.", + "routerUpdated": "Router updated.", + "routerDeleted": "Router deleted.", + "exportCreated": "Export created.", + "binaryCreated": "Binary backup created.", + "connectionSuccessful": "Connection successful.", + "settingsSaved": "Settings saved.", + "testEmailSent": "Test e-mail sent.", + "testPushoverSent": "Test Pushover notification sent.", + "logsDeletedOlderThan": "Logs older than {{days}} days deleted.", + "passwordChanged": "Password changed.", + "connectionFailed": "Connection test failed.", + "sshKeyUnlocked": "SSH key unlocked.", + "settingsSaveFailed": "Could not save settings.", + "testEmailFailed": "Could not send the test email.", + "testPushoverFailed": "Could not send the test Pushover notification.", + "swosBetaProbeOk": "SwitchOS connectivity verified.", + "swosBetaProbeFailed": "Could not verify SwitchOS access.", + "swosBetaDownloadOk": "SwitchOS backup downloaded.", + "swosBetaDownloadFailed": "Could not download the SwitchOS backup." + }, + "confirm": { + "header": "Confirmation", + "deleteBackup": "Delete this backup file?", + "deleteSelectedFiles": "Delete {{count}} selected files?", + "deleteRouterWithFiles": "Delete the router and all related files?", + "deleteLogsOlderThan": "Delete logs older than {{days}} days?" + }, + "footer": { + "authorLabel": "Author", + "apiLabel": "API", + "apiOnline": "online", + "apiOffline": "offline", + "apiChecking": "checking", + "apiLatencyLabel": "API latency", + "apiDocs": "API docs", + "apiOfflineTitle": "API connection lost", + "apiOfflineMessage": "The backend is not responding. Some features may be temporarily unavailable.", + "retry": "Retry" + }, + "diffConfigs": { + "title": "Config diff", + "eyebrow": "export comparison", + "subtitle": "Dedicated workspace for convenient RouterOS configuration comparisons.", + "exportsCard": "Exports for diff", + "exportsCardHint": ".rsc files in the current scope", + "scopeCard": "Scope", + "scopeCardHint": "Selected router or whole fleet", + "scopeTag": "Scope", + "readyCard": "Pair", + "readyCardHint": "Selection state for comparison", + "readyTag": "State", + "lastDiffCard": "Last diff", + "lastDiffCardHint": "Last opened file pair", + "lastDiffTag": "History", + "workspaceTitle": "Comparison workspace", + "workspaceSubtitle": "Pick a router, set older and newer export, then open the diff in a modal.", + "tableTitle": "Exports to pick from", + "tableSubtitle": "Quickly assign older and newer files and preview them without leaving the page.", + "waitingTag": "Waiting", + "noneSelected": "None" + }, + "switchosBeta": { + "title": "SwitchOS beta", + "eyebrow": "switchos / beta build", + "subtitle": "Standalone module for pulling SwitchOS backups without wiring it into the main repository.", + "betaTag": "Untested beta", + "summaryStandaloneValue": "Standalone", + "summaryStandaloneLabel": "Runs outside the main flow", + "summaryProtocolLabel": "Target protocol", + "summaryArtifactLabel": "Backup format", + "warningTitle": "Module status", + "warningSubtitle": "Separate working path prepared for SwitchOS web scraping.", + "warningHeadline": "This tab is marked as an untested beta build.", + "warningBody": "It does not save devices or files into the existing RouterOS inventory. It is meant for manual access checks and direct SwitchOS backup downloads.", + "formTitle": "Device details", + "formSubtitle": "Enter the switch address and the credentials used in the web UI.", + "label": "File label", + "labelPlaceholder": "for example css326-warehouse", + "host": "Host / URL", + "hostPlaceholder": "for example 192.168.88.1 or http://192.168.88.1", + "port": "Port", + "username": "Username", + "password": "Password", + "passwordPlaceholder": "Leave empty when the device has no password", + "probeButton": "Check access", + "downloadButton": "Download backup .swb", + "resultTitle": "Connection result", + "resultSubtitle": "Quick preview of the device response before downloading the file.", + "resultEmpty": "Check device access first or download the backup right away.", + "baseUrl": "Base URL", + "httpStatus": "HTTP status", + "authMode": "Auth mode", + "pageTitle": "Page title", + "serverHeader": "Server header", + "backupEndpoint": "Backup endpoint", + "available": "Available", + "unavailable": "Unavailable", + "genericError": "The SwitchOS beta operation could not be completed." + } +} diff --git a/frontend/src/assets/i18n/es.json b/frontend/src/assets/i18n/es.json new file mode 100644 index 0000000..c519057 --- /dev/null +++ b/frontend/src/assets/i18n/es.json @@ -0,0 +1,541 @@ +{ + "app": { + "menu": "Menú" + }, + "sidebar": { + "title": "copia de MikroTik", + "subtitle": "gestor de RouterOS/SwitchOS" + }, + "topbar": { + "caption": "mikrotik / centro de control", + "role": "administrador", + "languageSelector": "Selector de idioma" + }, + "common": { + "apply": "Aplicar", + "reset": "Restablecer", + "delete": "Eliminar", + "confirm": "Confirmar", + "cancel": "Cancelar", + "download": "Descargar", + "email": "Enviar correo", + "preview": "Vista previa", + "restore": "Restaurar", + "actions": "Acciones", + "open": "Abrir", + "edit": "Editar", + "diff": "Diff", + "ok": "OK", + "idle": "Sin datos", + "asc": "Ascendente", + "desc": "Descendente", + "enabled": "Activado", + "disabled": "Desactivado", + "failed": "Error" + }, + "nav": { + "dashboard": "Panel", + "routers": "Dispositivos", + "files": "Repositorio", + "settings": "Ajustes", + "logs": "Registros", + "logout": "Cerrar sesión", + "theme": "Tema", + "changePassword": "Cambiar contraseña", + "diffConfigs": "Diff de configuración", + "switchosBeta": "SwitchOS beta" + }, + "auth": { + "username": "Usuario", + "password": "Contraseña", + "login": "Iniciar sesión", + "register": "Registrarse", + "confirmPassword": "Confirmar contraseña", + "changePassword": "Cambiar contraseña", + "currentPassword": "Contraseña actual", + "newPassword": "Nueva contraseña", + "backToLogin": "Volver al inicio de sesión", + "backToApp": "Volver a la app", + "loginSubtitle": "Inicia sesión para continuar.", + "loginFailed": "Error de inicio de sesión", + "accountCreated": "Cuenta creada", + "registrationFailed": "Error de registro", + "passwordsMismatch": "Las contraseñas no coinciden", + "changePasswordFailed": "No se pudo cambiar la contraseña", + "securityEyebrow": "cuenta / seguridad", + "changePasswordSubtitle": "Actualiza la contraseña del administrador sin desorden visual.", + "changePasswordCardSubtitle": "Introduce la contraseña actual y define las nuevas credenciales.", + "passwordPanelSubtitle": "Comprobación rápida de fuerza y coincidencia antes de guardar.", + "passwordStrength": "Fuerza de la contraseña", + "passwordWeak": "Débil", + "passwordMedium": "Media", + "passwordStrong": "Fuerte", + "ruleLength": "Al menos 8 caracteres", + "ruleDigit": "Al menos un número", + "ruleMatch": "Ambos campos coinciden", + "passwordsMatchHint": "La nueva contraseña y la confirmación coinciden." + }, + "dashboard": { + "title": "Panel", + "eyebrow": "inicio / panel", + "subtitle": "Resumen de copias, exportaciones y actividad operativa en un solo lugar.", + "exportAll": "Exportar todo", + "binaryAll": "Copia binaria", + "managedRouters": "Dispositivos", + "managedRoutersHint": "Todos los dispositivos gestionados", + "inventoryTag": "Flota", + "exportsCard": "Exportaciones", + "exportsHint": "Instantáneas legibles de configuración", + "textTag": "Texto", + "binaryCard": "Copias binarias", + "binaryHint": "Puntos de recuperación", + "binaryTag": "Binario", + "allFilesCard": "Todos los archivos", + "allFilesHint": "Artefactos en el repositorio", + "archiveTag": "Archivo", + "storageTitle": "Uso del almacenamiento", + "storageSubtitle": "Resumen actual del uso del repositorio y del espacio libre.", + "folderUsage": "Uso de la carpeta", + "diskUsage": "Uso del disco", + "totalDisk": "Disco total", + "freeSpace": "Espacio libre", + "activityTitle": "Actividad reciente", + "activitySubtitle": "Últimos eventos operativos del backend.", + "noActivity": "No hay eventos recientes para mostrar.", + "avgBackupsPerRouter": "Prom. copias / router", + "activitySuccess": "Tarea completada", + "activityFailure": "Requiere atención", + "activityMaintenance": "Mantenimiento", + "activityDelivery": "Entrega", + "operationsTitle": "Centro de operaciones", + "operationsSubtitle": "Acciones principales e indicadores en vivo del repositorio en un solo lugar.", + "latestSnapshot": "Última instantánea", + "coverageLabel": "Cobertura de la flota", + "coverageHint": "Routers con al menos una copia", + "weeklyActivityLabel": "Actividad de 7 días", + "weeklyActivityHint": "Nuevas copias creadas esta semana", + "busiestRouterLabel": "Router más activo", + "routerSnapshotsHint": "{{count}} instantáneas en el repositorio", + "exportShareLabel": "Cuota de exportaciones", + "activityTodayLabel": "Eventos hoy", + "noneLabel": "Ninguno", + "activityTodayHint": "Entradas creadas hoy", + "usedSpace": "Espacio usado", + "storageViewCapacity": "Capacidad", + "storageViewCapacityHint": "Disco, uso del repositorio y espacio libre en una sola escala.", + "storageViewMix": "Tipos de copias", + "storageViewMixHint": "Distribución de todas las copias entre exportaciones de texto y copias binarias.", + "storageViewActivity": "Actividad 7 días", + "storageViewActivityHint": "Número de nuevas copias creadas en los últimos siete días.", + "storageViewRouters": "Routers principales", + "storageViewRoutersHint": "Dispositivos con mayor número de instantáneas en el repositorio.", + "storageChartEmpty": "Todavía no hay datos suficientes para dibujar este gráfico.", + "storageSnapshotTitle": "Métricas del repositorio", + "storageSnapshotHint": "Vista rápida de los indicadores más importantes de almacenamiento y copias." + }, + "routers": { + "title": "Dispositivos", + "detailTitle": "Detalles del dispositivo", + "add": "Agregar dispositivo", + "eyebrow": "inventario de dispositivos", + "subtitle": "Administra dispositivos RouterOS y SwitchOS y sus copias.", + "registeredDevices": "Dispositivos registrados", + "fleetTag": "Flota", + "sshPassword": "Contraseña", + "passwordHint": "Acceso con contraseña", + "credsTag": "Credenciales", + "sshKey": "Clave SSH", + "keyHint": "Acceso con clave", + "securityTag": "Seguridad", + "defaultPort": "Puerto 22", + "defaultPortHint": "Endpoints SSH estándar", + "portTag": "Puerto", + "listTitle": "Lista de dispositivos", + "listSubtitle": "Vista unificada para RouterOS y SwitchOS.", + "name": "Nombre", + "endpoint": "Endpoint", + "access": "Acceso", + "routerOsTarget": "Objetivo RouterOS", + "passwordMode": "Contraseña", + "noPassword": "Sin contraseña", + "keyMode": "Clave", + "globalKeyMode": "Clave global", + "noKey": "Sin clave", + "createDialogTitle": "Agregar dispositivo", + "editDialogTitle": "Editar dispositivo", + "host": "Host", + "port": "Puerto", + "sshUser": "Usuario", + "sshPrivateKey": "Clave privada SSH", + "optionalPassword": "Contraseña opcional", + "optionalPrivateKey": "Clave privada opcional", + "saveRouter": "Guardar dispositivo", + "profileEyebrow": "perfil del router", + "detailSubtitle": "Operaciones del dispositivo e historial de copias", + "exportOne": "Exportar", + "binaryOne": "Binario", + "testConnection": "Probar conexión", + "deleteRouter": "Eliminar router", + "exportsLabel": "Exportaciones", + "exportsLabelHint": "Instantáneas de texto", + "binaryLabel": "Copias binarias", + "binaryLabelHint": "Imágenes de recuperación", + "connectionLabel": "Conexión", + "connectionLabelHint": "Estado de la última prueba automática o manual", + "probeTag": "Prueba", + "accessTag": "Acceso", + "sshUserHint": "Usuario SSH actual", + "deviceStatusTitle": "Estado del dispositivo", + "deviceStatusSubtitle": "Metadatos guardados de la última prueba automática o manual de conexión.", + "connectionStateTitle": "Estado de la conexión", + "lastTestAt": "Última prueba", + "hostname": "Hostname", + "model": "Modelo", + "version": "Versión", + "uptime": "Uptime", + "lastError": "Último error", + "deviceStatusManualHint": "Las comprobaciones automáticas usan el intervalo de ajustes. La prueba manual sigue disponible.", + "noConnection": "Aún no hay una prueba de conexión guardada. Ejecuta una prueba manual o activa las comprobaciones automáticas en ajustes.", + "previewTitle": "Vista previa de exportación", + "previewSubtitle": "Último archivo de exportación abierto.", + "noPreview": "Selecciona un archivo de exportación para ver su contenido.", + "diffTitle": "Último diff", + "diffSubtitle": "Diferencia respecto a la exportación más reciente.", + "exportsTableTitle": "Exportaciones", + "exportsTableSubtitle": "Instantáneas legibles de RouterOS.", + "binaryTableTitle": "Copias binarias", + "binaryTableSubtitle": "Archivos listos para restaurar el dispositivo.", + "summaryKeyAccess": "con acceso por clave", + "summaryPasswordAccess": "con acceso por contraseña", + "previewModalHint": "La última exportación abierta está disponible en un modal.", + "openPreviewModal": "Abrir vista previa", + "diffModalHint": "El último diff cargado está disponible en un modal.", + "openDiffModal": "Abrir diff", + "noDiff": "Elige una exportación y ejecuta un diff para ver la última comparación.", + "routeros": "RouterOS", + "switchos": "SwitchOS", + "deviceType": "Tipo de dispositivo", + "defaultCredentials": "Credenciales por defecto", + "localCredentials": "Credenciales locales", + "noCredentials": "Sin credenciales", + "switchUserPlaceholder": "Vacío = usar ajustes", + "switchPasswordPlaceholder": "Vacío = usar ajustes", + "switchDefaultsHint": "Para SwitchOS puedes dejar usuario y contraseña vacíos para usar los valores por defecto.", + "downloadSwitchBackup": "Descargar copia", + "httpStatus": "Estado HTTP", + "serverHeader": "Cabecera Server", + "authMode": "Modo de autenticación", + "backupEndpoint": "Endpoint de copia", + "backupAvailable": "Disponible", + "backupUnavailable": "No disponible", + "connectionSectionTitle": "Perfil de conexión", + "connectionSectionHint": "Identidad básica del dispositivo y endpoint usado para alcanzarlo.", + "credentialsSectionTitle": "Acceso y credenciales", + "routerDialogSubtitle": "Configura el endpoint del dispositivo, los datos SSH y el método de acceso preferido.", + "switchDialogSubtitle": "Configura el endpoint de SwitchOS y las credenciales locales u opcionales compartidas desde ajustes." + }, + "files": { + "title": "Repositorio", + "eyebrow": "repositorio de artefactos", + "subtitle": "Busca, compara y entrega copias desde una vista clara.", + "downloadZip": "Descargar ZIP", + "visibleFiles": "Archivos visibles", + "visibleFilesHint": "Resultado del filtro actual", + "liveTag": "En vivo", + "selected": "Seleccionados", + "selectedHint": "Listos para acciones masivas", + "batchTag": "Lote", + "exportsCard": "Exportaciones", + "exportsHint": "Instantáneas de configuración", + "binaryCard": "Copias binarias", + "binaryHint": "Imágenes de recuperación", + "filtersTitle": "Filtros", + "filtersSubtitle": "Refina la lista por router, tipo o palabra clave.", + "searchLabel": "Buscar", + "searchPlaceholder": "Buscar por archivo o router", + "typeLabel": "Tipo", + "routerLabel": "Dispositivo", + "dateLabel": "Fecha", + "datePlaceholder": "Selecciona una fecha", + "sortLabel": "Ordenar por", + "orderLabel": "Orden", + "allTypes": "Todos los tipos", + "allRouters": "Todos los dispositivos", + "sortNewest": "Más nuevo", + "sortName": "Nombre", + "sortRouter": "Dispositivo", + "sortType": "Tipo", + "tableTitle": "Tabla del repositorio", + "tableSubtitle": "Artefactos disponibles para descarga, correo y restauración.", + "compareHint": "Selecciona exactamente dos archivos .rsc para compararlos.", + "compareSelected": "Comparar exportaciones seleccionadas", + "fileColumn": "Archivo", + "typeColumn": "Tipo", + "routerColumn": "Dispositivo", + "createdColumn": "Creado", + "actionsColumn": "Acciones", + "checksum": "Checksum", + "exportType": "Exportación", + "binaryType": "Copia binaria", + "previewDialogTitle": "Vista previa de exportación", + "diffDialogTitle": "Diff de exportación", + "openHtmlDiff": "Abrir diff HTML", + "sizeColumn": "Tamaño", + "compareColumn": "Comparar", + "compareOlder": "Archivo anterior", + "compareNewer": "Archivo más nuevo", + "pickOlder": "Seleccionar copia anterior", + "pickNewer": "Seleccionar copia más nueva", + "compareLatestPair": "Último par", + "setOlder": "Marcar como anterior", + "setNewer": "Marcar como más nuevo", + "latestForRouter": "Diff del router", + "binaryNoCompare": "Diff disponible solo para .rsc", + "openPlainDiff": "Mostrar diff plano", + "minutesAgo": "hace {{value}} min", + "hoursAgo": "hace {{value}} h", + "daysAgo": "hace {{value}} d", + "compareTitle": "Comparación de exportaciones", + "compareSubtitle": "Selecciona dos archivos .rsc y ejecuta el diff sin revisar toda la tabla.", + "exportPoolLabel": "exportaciones listas para comparar", + "compareSelectionHint": "Selecciona un archivo anterior y uno más nuevo", + "compareReadySameRouter": "Par listo · router {{router}}", + "compareReadyMixedRouters": "Par listo · routers mezclados" + }, + "settings": { + "title": "Ajustes", + "eyebrow": "configuración de la plataforma", + "subtitle": "Controla horarios, retención, notificaciones, pruebas de conexión y datos SSH compartidos.", + "testEmail": "Probar correo", + "testPushover": "Probar Pushover", + "retentionTitle": "Retención", + "retentionSubtitle": "Ventanas automáticas de limpieza para archivos y registros.", + "backupRetentionDays": "Días de retención de copias", + "logRetentionDays": "Días de retención de registros", + "retentionCron": "Cron de retención", + "automationTitle": "Automatización", + "automationSubtitle": "Horarios para exportaciones, copias binarias, retención y comprobaciones de conexión.", + "enableAutoExport": "Activar exportación automática", + "enableAutoExportHint": "Ejecuta exportaciones con las reglas cron de abajo.", + "exportCron": "Cron de exportación", + "binaryCron": "Cron binario", + "notificationsTitle": "Notificaciones", + "notificationsSubtitle": "Configuración de entrega SMTP y Pushover.", + "smtpEnabled": "SMTP activado", + "smtpEnabledHint": "Envía notificaciones a través de la pasarela SMTP.", + "failuresOnly": "Solo fallos", + "failuresOnlyHint": "Limita las alertas a trabajos fallidos.", + "smtpHost": "Host SMTP", + "smtpPort": "Puerto SMTP", + "smtpLogin": "Login SMTP", + "smtpPassword": "Contraseña SMTP", + "recipientEmail": "Correo del destinatario", + "pushoverToken": "Token de Pushover", + "pushoverUserKey": "Clave de usuario de Pushover", + "pushoverTokenPlaceholder": "Token de la aplicación", + "pushoverUserKeyPlaceholder": "Clave de usuario", + "sshDefaultsTitle": "Credenciales predeterminadas", + "sshDefaultsSubtitle": "Clave SSH compartida y acceso por defecto de SwitchOS usados por los dispositivos gestionados.", + "globalSshPrivateKey": "Clave privada SSH global", + "globalSshPrivateKeyPlaceholder": "Pega la clave privada PEM u OpenSSH", + "globalSshPrivateKeyHiddenPlaceholder": "La clave guardada está oculta. Introduce la contraseña arriba para verla o pega aquí una nueva clave para reemplazarla.", + "save": "Guardar ajustes", + "scheduleDisabled": "Desactivado", + "scheduleDaily": "Diario", + "scheduleWeekly": "Semanal", + "scheduleCustom": "Cron personalizado", + "scheduleMode": "Modo de horario", + "scheduleTime": "Hora", + "scheduleWeekday": "Día de la semana", + "weekdayMonday": "Lunes", + "weekdayTuesday": "Martes", + "weekdayWednesday": "Miércoles", + "weekdayThursday": "Jueves", + "weekdayFriday": "Viernes", + "weekdaySaturday": "Sábado", + "weekdaySunday": "Domingo", + "scheduleDisabledHint": "La tarea no se ejecutará automáticamente.", + "scheduleDailySummary": "Cada día a las {{time}}", + "scheduleWeeklySummary": "Cada {{weekday}} a las {{time}}", + "scheduleCustomEmpty": "Introduce una expresión cron personalizada", + "statusEnabled": "Activado", + "statusDisabled": "Desactivado", + "noNextRun": "No hay próxima ejecución programada", + "exportScheduleTitle": "Exportaciones de texto", + "binaryScheduleTitle": "Copias binarias", + "automationPlannerTitle": "Planificador de tareas", + "automationPlannerSubtitle": "Cada tarea tiene su propio horario, así que exportación, copia binaria y retención pueden ejecutarse en ventanas separadas.", + "automationPlannerTag": "Ventanas flexibles", + "exportPlannerHint": "Decide cuándo crear exportaciones de texto legibles. El modo desactivado detiene la automatización.", + "binaryPlannerHint": "Ventana separada para copias binarias completas cuando necesitas puntos de restauración.", + "retentionPlannerHint": "La retención limpia copias y registros antiguos según su propio horario.", + "connectionTestsTitle": "Pruebas automáticas de conexión", + "connectionTestsHint": "La aplicación puede actualizar el estado del router automáticamente. Pon 0 para desactivar las pruebas automáticas.", + "connectionTestIntervalMinutes": "Comprobar cada X minutos", + "connectionTestsEverySummary": "Cada {{minutes}} minutos", + "connectionTestsDisabledHint": "Las pruebas automáticas de conexión están desactivadas.", + "sshKeyHelper": "Mantén la clave SSH compartida en la columna derecha. Solo se revela tras confirmar la contraseña de tu cuenta.", + "sshKeyStoredTag": "Clave guardada", + "sshKeyWillBeRemovedTag": "Se eliminará", + "sshRevealHint": "La clave actual permanece oculta hasta que confirmes tu contraseña. También puedes pegar una clave nueva abajo para reemplazarla.", + "revealSshPassword": "Contraseña actual de la cuenta", + "revealSshPasswordPlaceholder": "Introduce la contraseña para revelar la clave", + "revealSshKey": "Revelar clave", + "hideSshKey": "Ocultar clave", + "clearSshKey": "Borrar clave", + "sshKeyClearNotice": "La clave SSH compartida guardada se eliminará al guardar.", + "sshRevealPasswordRequired": "Introduce tu contraseña actual para revelar la clave SSH.", + "sshRevealPasswordInvalid": "La contraseña usada para revelar la clave SSH no es válida.", + "schedulerAutoExportLabel": "Exportaciones automáticas", + "schedulerBinaryLabel": "Copias binarias", + "schedulerRetentionLabel": "Limpieza por retención", + "schedulerConnectionLabel": "Comprobaciones de conexión", + "schedulerLogsLabel": "Limpieza de registros", + "schedulerLogsDescription": "Cada 24 horas", + "schedulerCronDescription": "{{description}}", + "schedulerInvalidCron": "Expresión cron no válida", + "interfaceTitle": "Configuración de la interfaz", + "interfaceSubtitle": "Preferencias de idioma y tipografía guardadas para tu cuenta.", + "interfacePreferencesTitle": "Apariencia del espacio de trabajo", + "interfacePreferencesHint": "Elige el idioma predeterminado y la familia tipográfica para toda la aplicación.", + "interfacePreferencesTag": "Por usuario", + "fontFamily": "Familia tipográfica", + "fontDefault": "Predeterminada", + "switchosDefaultsTitle": "Credenciales por defecto de SwitchOS", + "switchosDefaultsHint": "Se usan cuando un dispositivo SwitchOS no tiene usuario o contraseña local.", + "defaultSwitchosUsername": "Usuario SwitchOS por defecto", + "defaultSwitchosPassword": "Contraseña SwitchOS por defecto" + }, + "logs": { + "title": "Registros", + "eyebrow": "historial operativo", + "subtitle": "Audita los últimos eventos de exportación, restauración y mantenimiento.", + "daysPlaceholder": "días", + "deleteOlderThan": "Eliminar anteriores a", + "entriesLabel": "Entradas", + "entriesHint": "Filas cargadas", + "auditTag": "Auditoría", + "retentionLabel": "Retención", + "retentionHint": "Umbral de limpieza", + "policyTag": "Política", + "daysSuffix": "días", + "tableTitle": "Tabla de registros", + "tableSubtitle": "Lista cronológica de operaciones capturadas por el backend.", + "timestampColumn": "Marca de tiempo", + "messageColumn": "Mensaje", + "retentionInfoLabel": "Retención de registros configurada" + }, + "toast": { + "success": "Hecho", + "info": "Info", + "error": "Error", + "exportPreviewLoaded": "Vista previa de exportación cargada.", + "backupSentEmail": "Copia enviada por correo.", + "binaryUploaded": "Copia binaria subida al router.", + "backupDeleted": "Copia eliminada.", + "selectedBackupsDeleted": "Copias seleccionadas eliminadas.", + "diffLoaded": "Diff cargado.", + "archivePrepared": "Archivo preparado.", + "exportedRouters": "Exportación completada para {{count}} routers.", + "binaryCompletedRouters": "Copia binaria completada para {{count}} routers.", + "routerCreated": "Router creado.", + "routerUpdated": "Router actualizado.", + "routerDeleted": "Router eliminado.", + "exportCreated": "Exportación creada.", + "binaryCreated": "Copia binaria creada.", + "connectionSuccessful": "Conexión correcta.", + "connectionFailed": "La prueba de conexión falló.", + "settingsSaved": "Ajustes guardados.", + "testEmailSent": "Correo de prueba enviado.", + "testPushoverSent": "Notificación de prueba de Pushover enviada.", + "logsDeletedOlderThan": "Se eliminaron los registros anteriores a {{days}} días.", + "passwordChanged": "Contraseña cambiada.", + "sshKeyUnlocked": "Clave SSH desbloqueada.", + "settingsSaveFailed": "No se pudieron guardar los ajustes.", + "testEmailFailed": "No se pudo enviar el correo de prueba.", + "testPushoverFailed": "No se pudo enviar la notificación de prueba de Pushover.", + "swosBetaProbeOk": "Conectividad de SwitchOS verificada.", + "swosBetaProbeFailed": "No se pudo verificar el acceso a SwitchOS.", + "swosBetaDownloadOk": "Backup de SwitchOS descargado.", + "swosBetaDownloadFailed": "No se pudo descargar el backup de SwitchOS." + }, + "confirm": { + "header": "Confirmación", + "deleteBackup": "¿Eliminar este archivo de copia?", + "deleteSelectedFiles": "¿Eliminar {{count}} archivos seleccionados?", + "deleteRouterWithFiles": "¿Eliminar el router y todos los archivos relacionados?", + "deleteLogsOlderThan": "¿Eliminar registros anteriores a {{days}} días?" + }, + "footer": { + "authorLabel": "Autor", + "apiLabel": "API", + "apiOnline": "en línea", + "apiOffline": "sin conexión", + "apiChecking": "comprobando", + "apiLatencyLabel": "Latencia API", + "apiDocs": "Docs API", + "apiOfflineTitle": "Conexión API perdida", + "apiOfflineMessage": "El backend no responde. Algunas funciones pueden no estar disponibles temporalmente.", + "retry": "Reintentar" + }, + "diffConfigs": { + "title": "Diff de configuración", + "eyebrow": "comparación de exportaciones", + "subtitle": "Vista dedicada para comparar configuraciones de RouterOS con mejor UX.", + "exportsCard": "Exportaciones para diff", + "exportsCardHint": "Archivos .rsc en el alcance actual", + "scopeCard": "Alcance", + "scopeCardHint": "Router seleccionado o toda la flota", + "scopeTag": "Alcance", + "readyCard": "Par", + "readyCardHint": "Estado de selección para comparar", + "readyTag": "Estado", + "lastDiffCard": "Último diff", + "lastDiffCardHint": "Último par abierto", + "lastDiffTag": "Historial", + "workspaceTitle": "Espacio de comparación", + "workspaceSubtitle": "Elige un router, define exportación antigua y nueva y abre el diff en un modal.", + "tableTitle": "Exportaciones para elegir", + "tableSubtitle": "Asignación rápida de archivos y vista previa sin salir de la página.", + "waitingTag": "Esperando", + "noneSelected": "Ninguno" + }, + "switchosBeta": { + "title": "SwitchOS beta", + "eyebrow": "switchos / beta", + "subtitle": "Módulo independiente para descargar copias de SwitchOS sin integrarlo con el repositorio principal.", + "betaTag": "Beta sin probar", + "summaryStandaloneValue": "Aislado", + "summaryStandaloneLabel": "Funciona fuera del flujo principal", + "summaryProtocolLabel": "Protocolo objetivo", + "summaryArtifactLabel": "Formato de copia", + "warningTitle": "Estado del módulo", + "warningSubtitle": "Ruta de trabajo separada preparada para scraping web de SwitchOS.", + "warningHeadline": "Esta pestaña está marcada como una beta sin probar.", + "warningBody": "No guarda dispositivos ni archivos en el inventario RouterOS existente. Sirve para comprobar acceso manualmente y descargar el backup de SwitchOS.", + "formTitle": "Datos del dispositivo", + "formSubtitle": "Introduce la dirección del switch y las credenciales usadas en la interfaz web.", + "label": "Etiqueta del archivo", + "labelPlaceholder": "por ejemplo css326-almacen", + "host": "Host / URL", + "hostPlaceholder": "por ejemplo 192.168.88.1 o http://192.168.88.1", + "port": "Puerto", + "username": "Usuario", + "password": "Contraseña", + "passwordPlaceholder": "Déjalo vacío si el equipo no tiene contraseña", + "probeButton": "Comprobar acceso", + "downloadButton": "Descargar backup .swb", + "resultTitle": "Resultado de conexión", + "resultSubtitle": "Vista rápida de la respuesta del equipo antes de descargar el archivo.", + "resultEmpty": "Primero comprueba el acceso al equipo o descarga el backup directamente.", + "baseUrl": "URL base", + "httpStatus": "Código HTTP", + "authMode": "Modo de autenticación", + "pageTitle": "Título de la página", + "serverHeader": "Cabecera del servidor", + "backupEndpoint": "Endpoint del backup", + "available": "Disponible", + "unavailable": "No disponible", + "genericError": "No se pudo completar la operación beta de SwitchOS." + } +} diff --git a/frontend/src/assets/i18n/no.json b/frontend/src/assets/i18n/no.json new file mode 100644 index 0000000..6595de9 --- /dev/null +++ b/frontend/src/assets/i18n/no.json @@ -0,0 +1,541 @@ +{ + "app": { + "menu": "Meny" + }, + "sidebar": { + "title": "MikroTik-backup", + "subtitle": "RouterOS/SwitchOS-behandler" + }, + "topbar": { + "caption": "mikrotik / kontrollsenter", + "role": "administrator", + "languageSelector": "Språkvalg" + }, + "common": { + "apply": "Bruk", + "reset": "Tilbakestill", + "delete": "Slett", + "confirm": "Bekreft", + "cancel": "Avbryt", + "download": "Last ned", + "email": "Send e-post", + "preview": "Forhåndsvisning", + "restore": "Gjenopprett", + "actions": "Handlinger", + "open": "Åpne", + "edit": "Rediger", + "diff": "Diff", + "ok": "OK", + "idle": "Ingen data", + "asc": "Stigende", + "desc": "Synkende", + "enabled": "På", + "disabled": "Av", + "failed": "Feilet" + }, + "nav": { + "dashboard": "Dashbord", + "routers": "Enheter", + "files": "Repository", + "settings": "Innstillinger", + "logs": "Logger", + "logout": "Logg ut", + "theme": "Tema", + "changePassword": "Bytt passord", + "diffConfigs": "Konfig-diff", + "switchosBeta": "SwitchOS beta" + }, + "auth": { + "username": "Brukernavn", + "password": "Passord", + "login": "Logg inn", + "register": "Registrer", + "confirmPassword": "Bekreft passord", + "changePassword": "Bytt passord", + "currentPassword": "Nåværende passord", + "newPassword": "Nytt passord", + "backToLogin": "Tilbake til innlogging", + "backToApp": "Tilbake til appen", + "loginSubtitle": "Logg inn for å fortsette.", + "loginFailed": "Innlogging mislyktes", + "accountCreated": "Konto opprettet", + "registrationFailed": "Registrering mislyktes", + "passwordsMismatch": "Passordene samsvarer ikke", + "changePasswordFailed": "Passordbytte mislyktes", + "securityEyebrow": "konto / sikkerhet", + "changePasswordSubtitle": "Oppdater administratorpassordet uten unødvendig visuell støy.", + "changePasswordCardSubtitle": "Skriv inn nåværende passord og sett nye legitimasjonsdata.", + "passwordPanelSubtitle": "Rask kontroll av styrke og samsvar før lagring.", + "passwordStrength": "Passordstyrke", + "passwordWeak": "Svak", + "passwordMedium": "Middels", + "passwordStrong": "Sterk", + "ruleLength": "Minst 8 tegn", + "ruleDigit": "Minst ett tall", + "ruleMatch": "Begge feltene samsvarer", + "passwordsMatchHint": "Det nye passordet og bekreftelsen samsvarer." + }, + "dashboard": { + "title": "Dashbord", + "eyebrow": "hjem / dashbord", + "subtitle": "Oversikt over backuper, eksportfiler og operativ aktivitet på ett sted.", + "exportAll": "Eksporter alle", + "binaryAll": "Binær backup", + "managedRouters": "Enheter", + "managedRoutersHint": "Alle administrerte enheter", + "inventoryTag": "Flåte", + "exportsCard": "Eksporter", + "exportsHint": "Lesbare konfigurasjonsøyeblikksbilder", + "textTag": "Tekst", + "binaryCard": "Binære backuper", + "binaryHint": "Gjenopprettingspunkter", + "binaryTag": "Binær", + "allFilesCard": "Alle filer", + "allFilesHint": "Artefakter i repositoryet", + "archiveTag": "Arkiv", + "storageTitle": "Lagringsbruk", + "storageSubtitle": "Nåværende oversikt over bruk og ledig plass i repositoryet.", + "folderUsage": "Mappebruk", + "diskUsage": "Diskbruk", + "totalDisk": "Total disk", + "freeSpace": "Ledig plass", + "activityTitle": "Nylig aktivitet", + "activitySubtitle": "Siste operative hendelser fra backend.", + "noActivity": "Ingen nylige hendelser å vise.", + "avgBackupsPerRouter": "Snitt backuper / ruter", + "activitySuccess": "Oppgave fullført", + "activityFailure": "Trenger oppmerksomhet", + "activityMaintenance": "Vedlikehold", + "activityDelivery": "Levering", + "operationsTitle": "Driftssenter", + "operationsSubtitle": "Viktigste handlinger og levende repositoryindikatorer på ett sted.", + "latestSnapshot": "Siste øyeblikksbilde", + "coverageLabel": "Flåtedekning", + "coverageHint": "Rutere med minst én backup", + "weeklyActivityLabel": "7-dagers aktivitet", + "weeklyActivityHint": "Nye backuper opprettet denne uken", + "busiestRouterLabel": "Mest aktive ruter", + "routerSnapshotsHint": "{{count}} øyeblikksbilder i repositoryet", + "exportShareLabel": "Eksportandel", + "activityTodayLabel": "Hendelser i dag", + "noneLabel": "Ingen", + "activityTodayHint": "Oppføringer opprettet i dag", + "usedSpace": "Brukt plass", + "storageViewCapacity": "Kapasitet", + "storageViewCapacityHint": "Disk, repositorybruk og ledig plass vist på samme skala.", + "storageViewMix": "Backuptyper", + "storageViewMixHint": "Fordeling av alle kopier mellom teksteksporter og binære backuper.", + "storageViewActivity": "7-dagers aktivitet", + "storageViewActivityHint": "Antall nye backuper opprettet de siste sju dagene.", + "storageViewRouters": "Topp-rutere", + "storageViewRoutersHint": "Enheter med flest øyeblikksbilder i repositoryet.", + "storageChartEmpty": "Det er ikke nok data til å tegne denne grafen ennå.", + "storageSnapshotTitle": "Repository-metrikker", + "storageSnapshotHint": "Rask oversikt over de viktigste lagrings- og backupindikatorene." + }, + "routers": { + "title": "Enheter", + "detailTitle": "Enhetsdetaljer", + "add": "Legg til enhet", + "eyebrow": "enhetsinventar", + "subtitle": "Administrer RouterOS- og SwitchOS-enheter og sikkerhetskopier.", + "registeredDevices": "Registrerte enheter", + "fleetTag": "Flåte", + "sshPassword": "Passord", + "passwordHint": "Passordbasert tilgang", + "credsTag": "Tilgang", + "sshKey": "SSH-nøkkel", + "keyHint": "Nøkkelbasert tilgang", + "securityTag": "Sikkerhet", + "defaultPort": "Port 22", + "defaultPortHint": "Standard SSH-endepunkter", + "portTag": "Port", + "listTitle": "Enhetsliste", + "listSubtitle": "Felles visning for RouterOS og SwitchOS.", + "name": "Navn", + "endpoint": "Endepunkt", + "access": "Tilgang", + "routerOsTarget": "RouterOS-mål", + "passwordMode": "Passord", + "noPassword": "Ingen passord", + "keyMode": "Nøkkel", + "globalKeyMode": "Global nøkkel", + "noKey": "Ingen nøkkel", + "createDialogTitle": "Legg til enhet", + "editDialogTitle": "Rediger enhet", + "host": "Vert", + "port": "Port", + "sshUser": "Bruker", + "sshPrivateKey": "SSH privat nøkkel", + "optionalPassword": "Valgfritt passord", + "optionalPrivateKey": "Valgfri privat nøkkel", + "saveRouter": "Lagre enhet", + "profileEyebrow": "ruterprofil", + "detailSubtitle": "Enhetsoperasjoner og backuphistorikk", + "exportOne": "Eksport", + "binaryOne": "Binær", + "testConnection": "Test tilkobling", + "deleteRouter": "Slett ruter", + "exportsLabel": "Eksporter", + "exportsLabelHint": "Tekstbaserte øyeblikksbilder", + "binaryLabel": "Binære backuper", + "binaryLabelHint": "Gjenopprettingsbilder", + "connectionLabel": "Tilkobling", + "connectionLabelHint": "Status fra siste automatiske eller manuelle test", + "probeTag": "Test", + "accessTag": "Tilgang", + "sshUserHint": "Gjeldende SSH-bruker", + "deviceStatusTitle": "Enhetsstatus", + "deviceStatusSubtitle": "Lagrede metadata fra siste automatiske eller manuelle tilkoblingstest.", + "connectionStateTitle": "Tilkoblingsstatus", + "lastTestAt": "Siste test", + "hostname": "Vertsnavn", + "model": "Modell", + "version": "Versjon", + "uptime": "Oppetid", + "lastError": "Siste feil", + "deviceStatusManualHint": "Automatiske kontroller bruker intervallet fra innstillingene. Manuell test er fortsatt tilgjengelig.", + "noConnection": "Ingen lagret tilkoblingstest ennå. Kjør en manuell test eller aktiver automatiske kontroller i innstillingene.", + "previewTitle": "Forhåndsvisning av eksport", + "previewSubtitle": "Sist åpnet eksportfil.", + "noPreview": "Velg en eksportfil for å se innholdet.", + "diffTitle": "Siste diff", + "diffSubtitle": "Forskjell mot nyeste eksport.", + "exportsTableTitle": "Eksporter", + "exportsTableSubtitle": "Lesbare RouterOS-øyeblikksbilder.", + "binaryTableTitle": "Binære backuper", + "binaryTableSubtitle": "Filer klare for gjenoppretting av enheten.", + "summaryKeyAccess": "med nøkkelbasert tilgang", + "summaryPasswordAccess": "med passordtilgang", + "previewModalHint": "Sist åpnet eksport er tilgjengelig i en modal.", + "openPreviewModal": "Åpne forhåndsvisning", + "diffModalHint": "Sist lastede diff er tilgjengelig i en modal.", + "openDiffModal": "Åpne diff", + "noDiff": "Velg en eksport og kjør diff for å se siste sammenligning.", + "routeros": "RouterOS", + "switchos": "SwitchOS", + "deviceType": "Enhetstype", + "defaultCredentials": "Standard legitimasjon", + "localCredentials": "Lokal legitimasjon", + "noCredentials": "Ingen legitimasjon", + "switchUserPlaceholder": "Tom = bruk innstillinger", + "switchPasswordPlaceholder": "Tom = bruk innstillinger", + "switchDefaultsHint": "For SwitchOS kan du la bruker og passord være tomme for å bruke standardverdier fra innstillinger.", + "downloadSwitchBackup": "Last ned backup", + "httpStatus": "HTTP-status", + "serverHeader": "Server-header", + "authMode": "Autentiseringsmodus", + "backupEndpoint": "Backup-endepunkt", + "backupAvailable": "Tilgjengelig", + "backupUnavailable": "Utilgjengelig", + "connectionSectionTitle": "Tilkoblingsprofil", + "connectionSectionHint": "Grunnleggende enhetsidentitet og endpoint som brukes for å nå den.", + "credentialsSectionTitle": "Tilgang og legitimasjon", + "routerDialogSubtitle": "Sett enhetens endpoint, SSH-data og foretrukket innloggingsmetode.", + "switchDialogSubtitle": "Sett SwitchOS-endpoint og valgfrie lokale eller delte standarddata fra innstillinger." + }, + "files": { + "title": "Repository", + "eyebrow": "artefaktrepository", + "subtitle": "Søk, sammenlign og lever backuper fra én tydelig visning.", + "downloadZip": "Last ned ZIP", + "visibleFiles": "Synlige filer", + "visibleFilesHint": "Resultat av gjeldende filter", + "liveTag": "Live", + "selected": "Valgte", + "selectedHint": "Klare for massehandlinger", + "batchTag": "Batch", + "exportsCard": "Eksporter", + "exportsHint": "Konfigurasjonsøyeblikksbilder", + "binaryCard": "Binære backuper", + "binaryHint": "Gjenopprettingsbilder", + "filtersTitle": "Filtre", + "filtersSubtitle": "Begrens listen etter ruter, type eller nøkkelord.", + "searchLabel": "Søk", + "searchPlaceholder": "Søk etter fil eller ruter", + "typeLabel": "Type", + "routerLabel": "Enhet", + "dateLabel": "Dato", + "datePlaceholder": "Velg dato", + "sortLabel": "Sorter etter", + "orderLabel": "Rekkefølge", + "allTypes": "Alle typer", + "allRouters": "Alle enheter", + "sortNewest": "Nyeste", + "sortName": "Navn", + "sortRouter": "Enhet", + "sortType": "Type", + "tableTitle": "Repositorytabell", + "tableSubtitle": "Artefakter tilgjengelige for nedlasting, e-post og gjenoppretting.", + "compareHint": "Velg nøyaktig to .rsc-filer for å sammenligne dem.", + "compareSelected": "Sammenlign valgte eksporter", + "fileColumn": "Fil", + "typeColumn": "Type", + "routerColumn": "Enhet", + "createdColumn": "Opprettet", + "actionsColumn": "Handlinger", + "checksum": "Checksum", + "exportType": "Eksport", + "binaryType": "Binær backup", + "previewDialogTitle": "Forhåndsvisning av eksport", + "diffDialogTitle": "Eksportdiff", + "openHtmlDiff": "Åpne HTML-diff", + "sizeColumn": "Størrelse", + "compareColumn": "Sammenlign", + "compareOlder": "Eldre fil", + "compareNewer": "Nyere fil", + "pickOlder": "Velg eldre backup", + "pickNewer": "Velg nyere backup", + "compareLatestPair": "Siste par", + "setOlder": "Sett som eldre", + "setNewer": "Sett som nyere", + "latestForRouter": "Ruterdiff", + "binaryNoCompare": "Diff tilgjengelig kun for .rsc", + "openPlainDiff": "Vis ren diff", + "minutesAgo": "{{value}} min siden", + "hoursAgo": "{{value}} t siden", + "daysAgo": "{{value}} d siden", + "compareTitle": "Sammenligning av eksporter", + "compareSubtitle": "Velg to .rsc-filer og start diff uten å grave gjennom hele tabellen.", + "exportPoolLabel": "eksporter klare for sammenligning", + "compareSelectionHint": "Velg en eldre og en nyere fil", + "compareReadySameRouter": "Par klart · ruter {{router}}", + "compareReadyMixedRouters": "Par klart · blandede rutere" + }, + "settings": { + "title": "Innstillinger", + "eyebrow": "plattformkonfigurasjon", + "subtitle": "Styr tidsplaner, retensjon, varsler, tilkoblingstester og delte SSH-data.", + "testEmail": "Test e-post", + "testPushover": "Test Pushover", + "retentionTitle": "Retensjon", + "retentionSubtitle": "Automatiske ryddevinduer for filer og logger.", + "backupRetentionDays": "Dager for backupretensjon", + "logRetentionDays": "Dager for loggretensjon", + "retentionCron": "Retensjons-cron", + "automationTitle": "Automatisering", + "automationSubtitle": "Tidsplaner for eksport, binære jobber, retensjon og tilkoblingskontroller.", + "enableAutoExport": "Aktiver automatisk eksport", + "enableAutoExportHint": "Kjør eksportjobber med cron-reglene nedenfor.", + "exportCron": "Eksport-cron", + "binaryCron": "Binær-cron", + "notificationsTitle": "Varsler", + "notificationsSubtitle": "SMTP- og Pushover-konfigurasjon.", + "smtpEnabled": "SMTP aktivert", + "smtpEnabledHint": "Send varsler via SMTP-gatewayen.", + "failuresOnly": "Kun feil", + "failuresOnlyHint": "Begrens varsler til mislykkede jobber.", + "smtpHost": "SMTP-vert", + "smtpPort": "SMTP-port", + "smtpLogin": "SMTP-innlogging", + "smtpPassword": "SMTP-passord", + "recipientEmail": "Mottaker e-post", + "pushoverToken": "Pushover-token", + "pushoverUserKey": "Pushover-brukernøkkel", + "pushoverTokenPlaceholder": "Applikasjonstoken", + "pushoverUserKeyPlaceholder": "Brukernøkkel", + "sshDefaultsTitle": "Standard legitimasjon", + "sshDefaultsSubtitle": "Delt SSH-nøkkel og standard innlogging for SwitchOS brukt på administrerte enheter.", + "globalSshPrivateKey": "Global SSH privat nøkkel", + "globalSshPrivateKeyPlaceholder": "Lim inn PEM- eller OpenSSH-privat nøkkel", + "globalSshPrivateKeyHiddenPlaceholder": "Den lagrede nøkkelen er skjult. Skriv inn passordet over for å se den, eller lim inn en ny nøkkel her for å erstatte den.", + "save": "Lagre innstillinger", + "scheduleDisabled": "Av", + "scheduleDaily": "Daglig", + "scheduleWeekly": "Ukentlig", + "scheduleCustom": "Egendefinert cron", + "scheduleMode": "Planmodus", + "scheduleTime": "Tid", + "scheduleWeekday": "Ukedag", + "weekdayMonday": "Mandag", + "weekdayTuesday": "Tirsdag", + "weekdayWednesday": "Onsdag", + "weekdayThursday": "Torsdag", + "weekdayFriday": "Fredag", + "weekdaySaturday": "Lørdag", + "weekdaySunday": "Søndag", + "scheduleDisabledHint": "Jobben vil ikke kjøre automatisk.", + "scheduleDailySummary": "Hver dag kl. {{time}}", + "scheduleWeeklySummary": "Hver {{weekday}} kl. {{time}}", + "scheduleCustomEmpty": "Skriv inn et egendefinert cron-uttrykk", + "statusEnabled": "På", + "statusDisabled": "Av", + "noNextRun": "Ingen neste kjøring planlagt", + "exportScheduleTitle": "Teksteksporter", + "binaryScheduleTitle": "Binære backuper", + "automationPlannerTitle": "Jobbplanlegger", + "automationPlannerSubtitle": "Hver jobb har sin egen plan, så eksport, binær backup og retensjon kan kjøre i separate vinduer.", + "automationPlannerTag": "Fleksible vinduer", + "exportPlannerHint": "Bestem når lesbare teksteksporter skal opprettes. Av-modus stopper automatiseringen helt.", + "binaryPlannerHint": "Separat vindu for fulle binære backuper når du trenger gjenopprettingspunkter.", + "retentionPlannerHint": "Retensjon rydder gamle backuper og logger etter sin egen plan.", + "connectionTestsTitle": "Automatiske tilkoblingstester", + "connectionTestsHint": "Appen kan oppdatere ruterstatus automatisk. Sett 0 for å deaktivere automatiske tester.", + "connectionTestIntervalMinutes": "Kontroller hver X minutt", + "connectionTestsEverySummary": "Hvert {{minutes}}. minutt", + "connectionTestsDisabledHint": "Automatiske tilkoblingstester er deaktivert.", + "sshKeyHelper": "Hold den delte SSH-nøkkelen i høyre kolonne. Vis den først etter at du har bekreftet passordet til kontoen din.", + "sshKeyStoredTag": "Lagret nøkkel", + "sshKeyWillBeRemovedTag": "Vil bli fjernet", + "sshRevealHint": "Gjeldende nøkkel forblir skjult til du bekrefter passordet ditt. Du kan også lime inn en ny nøkkel nedenfor for å erstatte den.", + "revealSshPassword": "Gjeldende kontopassord", + "revealSshPasswordPlaceholder": "Skriv inn passord for å vise nøkkelen", + "revealSshKey": "Vis nøkkel", + "hideSshKey": "Skjul nøkkel", + "clearSshKey": "Tøm nøkkel", + "sshKeyClearNotice": "Den lagrede delte SSH-nøkkelen blir fjernet når du lagrer.", + "sshRevealPasswordRequired": "Skriv inn gjeldende passord for å vise SSH-nøkkelen.", + "sshRevealPasswordInvalid": "Passordet som ble brukt for å vise SSH-nøkkelen er ugyldig.", + "schedulerAutoExportLabel": "Automatiske eksporter", + "schedulerBinaryLabel": "Binære backuper", + "schedulerRetentionLabel": "Retensjonsrydding", + "schedulerConnectionLabel": "Tilkoblingskontroller", + "schedulerLogsLabel": "Loggrydding", + "schedulerLogsDescription": "Hver 24. time", + "schedulerCronDescription": "{{description}}", + "schedulerInvalidCron": "Ugyldig cron-uttrykk", + "interfaceTitle": "Grensesnittkonfigurasjon", + "interfaceSubtitle": "Språk- og typografivalg som lagres for kontoen din.", + "interfacePreferencesTitle": "Utseende for arbeidsområdet", + "interfacePreferencesHint": "Velg standardspråk og skriftfamilie for hele applikasjonen.", + "interfacePreferencesTag": "Per bruker", + "fontFamily": "Skriftfamilie", + "fontDefault": "Standard", + "switchosDefaultsTitle": "Standard SwitchOS-legitimasjon", + "switchosDefaultsHint": "Brukes når en SwitchOS-enhet ikke har lokalt brukernavn eller passord.", + "defaultSwitchosUsername": "Standard SwitchOS-bruker", + "defaultSwitchosPassword": "Standard SwitchOS-passord" + }, + "logs": { + "title": "Logger", + "eyebrow": "driftshistorikk", + "subtitle": "Revider de siste eksport-, gjenopprettings- og vedlikeholdshendelsene.", + "daysPlaceholder": "dager", + "deleteOlderThan": "Slett eldre enn", + "entriesLabel": "Oppføringer", + "entriesHint": "Lastede rader", + "auditTag": "Revisjon", + "retentionLabel": "Retensjon", + "retentionHint": "Ryddeterskel", + "policyTag": "Policy", + "daysSuffix": "dager", + "tableTitle": "Loggtabell", + "tableSubtitle": "Kronologisk liste over operasjoner fanget av backend.", + "timestampColumn": "Tidsstempel", + "messageColumn": "Melding", + "retentionInfoLabel": "Konfigurert loggretensjon" + }, + "toast": { + "success": "Ferdig", + "info": "Info", + "error": "Feil", + "exportPreviewLoaded": "Forhåndsvisning av eksport lastet.", + "backupSentEmail": "Backup sendt på e-post.", + "binaryUploaded": "Binær backup lastet opp til ruteren.", + "backupDeleted": "Backup slettet.", + "selectedBackupsDeleted": "Valgte backuper slettet.", + "diffLoaded": "Diff lastet.", + "archivePrepared": "Arkiv klargjort.", + "exportedRouters": "Eksport fullført for {{count}} rutere.", + "binaryCompletedRouters": "Binær backup fullført for {{count}} rutere.", + "routerCreated": "Ruter opprettet.", + "routerUpdated": "Ruter oppdatert.", + "routerDeleted": "Ruter slettet.", + "exportCreated": "Eksport opprettet.", + "binaryCreated": "Binær backup opprettet.", + "connectionSuccessful": "Tilkobling vellykket.", + "connectionFailed": "Tilkoblingstesten mislyktes.", + "settingsSaved": "Innstillinger lagret.", + "testEmailSent": "Test-e-post sendt.", + "testPushoverSent": "Testvarsling for Pushover sendt.", + "logsDeletedOlderThan": "Logger eldre enn {{days}} dager slettet.", + "passwordChanged": "Passord endret.", + "sshKeyUnlocked": "SSH-nøkkel låst opp.", + "settingsSaveFailed": "Kunne ikke lagre innstillingene.", + "testEmailFailed": "Kunne ikke sende test-e-post.", + "testPushoverFailed": "Kunne ikke sende testvarsel via Pushover.", + "swosBetaProbeOk": "SwitchOS-tilkobling verifisert.", + "swosBetaProbeFailed": "Kunne ikke verifisere tilgang til SwitchOS.", + "swosBetaDownloadOk": "SwitchOS-backup lastet ned.", + "swosBetaDownloadFailed": "Kunne ikke laste ned SwitchOS-backup." + }, + "confirm": { + "header": "Bekreftelse", + "deleteBackup": "Slette denne backupfilen?", + "deleteSelectedFiles": "Slette {{count}} valgte filer?", + "deleteRouterWithFiles": "Slette ruteren og alle relaterte filer?", + "deleteLogsOlderThan": "Slette logger eldre enn {{days}} dager?" + }, + "footer": { + "authorLabel": "Forfatter", + "apiLabel": "API", + "apiOnline": "online", + "apiOffline": "offline", + "apiChecking": "sjekker", + "apiLatencyLabel": "API-forsinkelse", + "apiDocs": "API-dokumentasjon", + "apiOfflineTitle": "API-tilkoblingen er borte", + "apiOfflineMessage": "Backend svarer ikke. Noen funksjoner kan være midlertidig utilgjengelige.", + "retry": "Prøv igjen" + }, + "diffConfigs": { + "title": "Konfig-diff", + "eyebrow": "eksportsammenligning", + "subtitle": "Egen side for enklere sammenligning av RouterOS-konfigurasjoner.", + "exportsCard": "Eksporter for diff", + "exportsCardHint": ".rsc-filer i valgt område", + "scopeCard": "Område", + "scopeCardHint": "Valgt ruter eller hele flåten", + "scopeTag": "Område", + "readyCard": "Par", + "readyCardHint": "Valgstatus for sammenligning", + "readyTag": "Status", + "lastDiffCard": "Siste diff", + "lastDiffCardHint": "Sist åpnet filpar", + "lastDiffTag": "Historikk", + "workspaceTitle": "Sammenligningsflate", + "workspaceSubtitle": "Velg ruter, sett eldre og nyere eksport, og åpne diff i modal.", + "tableTitle": "Eksporter å velge fra", + "tableSubtitle": "Rask tildeling av eldre og nyere filer med forhåndsvisning på samme side.", + "waitingTag": "Venter", + "noneSelected": "Ingen" + }, + "switchosBeta": { + "title": "SwitchOS beta", + "eyebrow": "switchos / beta", + "subtitle": "Egen modul for å hente SwitchOS-kopier uten å koble den til hovedlageret.", + "betaTag": "Utestet beta", + "summaryStandaloneValue": "Separat", + "summaryStandaloneLabel": "Kjører utenfor hovedflyten", + "summaryProtocolLabel": "Målprotokoll", + "summaryArtifactLabel": "Backup-format", + "warningTitle": "Modulstatus", + "warningSubtitle": "Separat arbeidsløype forberedt for SwitchOS web scraping.", + "warningHeadline": "Denne fanen er merket som en utestet beta.", + "warningBody": "Den lagrer ikke enheter eller filer i den eksisterende RouterOS-listen. Den er ment for manuell tilgangssjekk og direkte nedlasting av SwitchOS-backup.", + "formTitle": "Enhetsdata", + "formSubtitle": "Oppgi switch-adresse og legitimasjonen som brukes i webgrensesnittet.", + "label": "Filnavn-etikett", + "labelPlaceholder": "for eksempel css326-lager", + "host": "Vert / URL", + "hostPlaceholder": "for eksempel 192.168.88.1 eller http://192.168.88.1", + "port": "Port", + "username": "Brukernavn", + "password": "Passord", + "passwordPlaceholder": "La stå tomt hvis enheten ikke har passord", + "probeButton": "Sjekk tilgang", + "downloadButton": "Last ned backup .swb", + "resultTitle": "Tilkoblingsresultat", + "resultSubtitle": "Rask forhåndsvisning av enhetens svar før filen lastes ned.", + "resultEmpty": "Sjekk tilgang først eller last ned backupen med en gang.", + "baseUrl": "Basis-URL", + "httpStatus": "HTTP-status", + "authMode": "Autentiseringsmodus", + "pageTitle": "Sidetittel", + "serverHeader": "Server-header", + "backupEndpoint": "Backup-endepunkt", + "available": "Tilgjengelig", + "unavailable": "Utilgjengelig", + "genericError": "SwitchOS beta-operasjonen kunne ikke fullføres." + } +} diff --git a/frontend/src/assets/i18n/pl.json b/frontend/src/assets/i18n/pl.json new file mode 100644 index 0000000..bb56566 --- /dev/null +++ b/frontend/src/assets/i18n/pl.json @@ -0,0 +1,541 @@ +{ + "app": { + "menu": "Menu" + }, + "sidebar": { + "title": "Mikrotik Backup System", + "subtitle": "Device backup platform" + }, + "topbar": { + "caption": "mikrotik / control center", + "role": "administrator", + "languageSelector": "Wybór języka" + }, + "common": { + "apply": "Zastosuj", + "reset": "Resetuj", + "delete": "Usuń", + "confirm": "Potwierdź", + "cancel": "Anuluj", + "download": "Pobierz", + "email": "Wyślij e-mail", + "preview": "Podgląd", + "restore": "Przywróć", + "actions": "Akcje", + "open": "Otwórz", + "edit": "Edytuj", + "diff": "Diff", + "ok": "OK", + "idle": "Brak", + "asc": "Rosnąco", + "desc": "Malejąco", + "enabled": "Włączone", + "disabled": "Wyłączone", + "failed": "Błąd" + }, + "nav": { + "dashboard": "Dashboard", + "routers": "Urządzenia", + "files": "Repozytorium", + "settings": "Ustawienia", + "logs": "Logi", + "logout": "Wyloguj", + "theme": "Motyw", + "changePassword": "Zmień hasło", + "diffConfigs": "Diff konfiguracji", + "switchosBeta": "SwitchOS beta" + }, + "auth": { + "username": "Użytkownik", + "password": "Hasło", + "login": "Zaloguj", + "register": "Rejestracja", + "confirmPassword": "Potwierdź hasło", + "changePassword": "Zmień hasło", + "currentPassword": "Obecne hasło", + "newPassword": "Nowe hasło", + "backToLogin": "Powrót do logowania", + "backToApp": "Powrót do aplikacji", + "loginSubtitle": "Zaloguj się, aby kontynuować.", + "loginFailed": "Logowanie nie powiodło się", + "accountCreated": "Konto zostało utworzone", + "registrationFailed": "Rejestracja nie powiodła się", + "passwordsMismatch": "Hasła nie są takie same", + "changePasswordFailed": "Zmiana hasła nie powiodła się", + "securityEyebrow": "konto / bezpieczeństwo", + "changePasswordSubtitle": "Zaktualizuj hasło administratora bez zbędnych ustawień dodatkowych.", + "changePasswordCardSubtitle": "Podaj obecne hasło i ustaw nowe dane logowania.", + "passwordPanelSubtitle": "Szybki podgląd siły hasła i zgodności pól przed zapisem.", + "passwordStrength": "Siła hasła", + "passwordWeak": "Słabe", + "passwordMedium": "Średnie", + "passwordStrong": "Mocne", + "ruleLength": "Minimum 8 znaków", + "ruleDigit": "Przynajmniej jedna cyfra", + "ruleMatch": "Oba pola są zgodne", + "passwordsMatchHint": "Nowe hasło i potwierdzenie są zgodne." + }, + "dashboard": { + "title": "Dashboard", + "eyebrow": "strona główna / dashboard", + "subtitle": "Przegląd backupów, eksportów i aktywności operacyjnej w jednym miejscu.", + "exportAll": "Eksportuj wszystko", + "binaryAll": "Backup binarny", + "managedRouters": "Urządzenia", + "managedRoutersHint": "Wszystkie zarządzane urządzenia", + "inventoryTag": "Flota", + "exportsCard": "Eksporty", + "exportsHint": "Czytelne snapshoty konfiguracji", + "textTag": "Tekst", + "binaryCard": "Backupy binarne", + "binaryHint": "Punkty odtworzenia", + "binaryTag": "Binary", + "allFilesCard": "Wszystkie pliki", + "allFilesHint": "Artefakty w repozytorium", + "archiveTag": "Archiwum", + "storageTitle": "Wykorzystanie przestrzeni", + "storageSubtitle": "Bieżący podgląd zajętości repozytorium i wolnego miejsca.", + "folderUsage": "Zajętość katalogu", + "diskUsage": "Użycie dysku", + "totalDisk": "Cały dysk", + "freeSpace": "Wolne miejsce", + "activityTitle": "Ostatnia aktywność", + "activitySubtitle": "Najnowsze zdarzenia operacyjne z backendu.", + "noActivity": "Brak ostatnich zdarzeń do wyświetlenia.", + "avgBackupsPerRouter": "Śr. backupów / router", + "activitySuccess": "Zadanie zakończone", + "activityFailure": "Wymaga uwagi", + "activityMaintenance": "Utrzymanie", + "activityDelivery": "Dystrybucja", + "operationsTitle": "Centrum operacji", + "operationsSubtitle": "Główne akcje i szybkie wskaźniki pracy repozytorium.", + "latestSnapshot": "Najnowszy snapshot", + "coverageLabel": "Pokrycie floty", + "coverageHint": "Routery z co najmniej jednym backupem", + "weeklyActivityLabel": "Aktywność 7 dni", + "weeklyActivityHint": "Nowe backupy z ostatniego tygodnia", + "busiestRouterLabel": "Najaktywniejszy router", + "routerSnapshotsHint": "{{count}} snapshotów w repozytorium", + "exportShareLabel": "Udział eksportów", + "activityTodayLabel": "Zdarzenia dzisiaj", + "noneLabel": "Brak", + "activityTodayHint": "Wpisy z bieżącego dnia", + "usedSpace": "Zajęte miejsce", + "storageViewCapacity": "Pojemność", + "storageViewCapacityHint": "Widok dysku, zajętości repozytorium i wolnego miejsca w jednej skali.", + "storageViewMix": "Typy backupów", + "storageViewMixHint": "Podział wszystkich kopii na eksporty tekstowe i backupy binarne.", + "storageViewActivity": "Aktywność 7 dni", + "storageViewActivityHint": "Liczba nowych backupów z ostatnich siedmiu dni.", + "storageViewRouters": "Top routery", + "storageViewRoutersHint": "Urządzenia z największą liczbą snapshotów w repozytorium.", + "storageChartEmpty": "Brak danych do narysowania wykresu.", + "storageSnapshotTitle": "Metryki repozytorium", + "storageSnapshotHint": "Szybki podgląd najważniejszych wskaźników przestrzeni i backupów." + }, + "routers": { + "title": "Urządzenia", + "detailTitle": "Szczegóły urządzenia", + "add": "Dodaj urządzenie", + "eyebrow": "inwentaryzacja urządzeń", + "subtitle": "Zarządzaj urządzeniami RouterOS i SwitchOS oraz ich kopiami.", + "registeredDevices": "Zarejestrowane urządzenia", + "fleetTag": "Flota", + "sshPassword": "Hasło", + "passwordHint": "Dostęp hasłem", + "credsTag": "Dostęp", + "sshKey": "Klucz SSH", + "keyHint": "Dostęp kluczem", + "securityTag": "Bezpieczeństwo", + "defaultPort": "Port 22", + "defaultPortHint": "Standardowe endpointy SSH", + "portTag": "Port", + "listTitle": "Lista urządzeń", + "listSubtitle": "Wspólny widok RouterOS i SwitchOS.", + "name": "Nazwa", + "endpoint": "Endpoint", + "access": "Dostęp", + "routerOsTarget": "Cel RouterOS", + "passwordMode": "Hasło", + "noPassword": "Bez hasła", + "keyMode": "Klucz", + "globalKeyMode": "Klucz globalny", + "noKey": "Bez klucza", + "createDialogTitle": "Dodaj urządzenie", + "editDialogTitle": "Edytuj urządzenie", + "host": "Host", + "port": "Port", + "sshUser": "Użytkownik", + "sshPrivateKey": "Klucz prywatny SSH", + "optionalPassword": "Opcjonalne hasło", + "optionalPrivateKey": "Opcjonalny klucz prywatny", + "saveRouter": "Zapisz urządzenie", + "profileEyebrow": "profil routera", + "detailSubtitle": "Operacje urządzenia i historia backupów", + "exportOne": "Eksport", + "binaryOne": "Backup", + "testConnection": "Test połączenia", + "deleteRouter": "Usuń router", + "exportsLabel": "Eksporty", + "exportsLabelHint": "Tekstowe snapshoty", + "binaryLabel": "Backupy binarne", + "binaryLabelHint": "Obrazy odzyskiwania", + "connectionLabel": "Połączenie", + "connectionLabelHint": "Status z ostatniego automatycznego lub ręcznego testu połączenia", + "probeTag": "Test", + "accessTag": "Dostęp", + "sshUserHint": "Efektywny login urządzenia", + "deviceStatusTitle": "Status urządzenia", + "deviceStatusSubtitle": "Zapisane metadane z ostatniego automatycznego lub ręcznego testu połączenia.", + "hostname": "Hostname", + "model": "Model", + "version": "Wersja", + "uptime": "Uptime", + "noConnection": "Brak zapisanego testu połączenia. Uruchom test ręczny albo włącz testy automatyczne w ustawieniach.", + "previewTitle": "Podgląd eksportu", + "previewSubtitle": "Ostatnio otwarty plik eksportu.", + "noPreview": "Wybierz plik eksportu, aby zobaczyć jego zawartość.", + "diffTitle": "Ostatni diff", + "diffSubtitle": "Różnice względem najnowszego eksportu.", + "exportsTableTitle": "Eksporty", + "exportsTableSubtitle": "Czytelne snapshoty RouterOS.", + "binaryTableTitle": "Backupy binarne", + "binaryTableSubtitle": "Pliki binarne i kopie SwitchOS.", + "summaryKeyAccess": "z dostępem kluczem", + "summaryPasswordAccess": "z dostępem hasłem", + "connectionStateTitle": "Stan połączenia", + "lastTestAt": "Ostatni test", + "lastError": "Ostatni błąd", + "deviceStatusManualHint": "Automatyczne testy używają interwału z ustawień. Ręczny test nadal jest dostępny.", + "previewModalHint": "Ostatnio otwarty eksport jest dostępny w modalu.", + "openPreviewModal": "Otwórz podgląd", + "diffModalHint": "Ostatnio załadowany diff jest dostępny w modalu.", + "openDiffModal": "Otwórz diff", + "noDiff": "Wybierz eksport i uruchom diff, aby zobaczyć ostatnie porównanie.", + "routeros": "RouterOS", + "switchos": "SwitchOS", + "deviceType": "Typ urządzenia", + "defaultCredentials": "Domyślne dane", + "localCredentials": "Lokalne dane", + "noCredentials": "Brak danych", + "switchUserPlaceholder": "Puste = z ustawień", + "switchPasswordPlaceholder": "Puste = z ustawień", + "switchDefaultsHint": "Dla SwitchOS możesz zostawić użytkownika i hasło puste, aby użyć wartości domyślnych z ustawień.", + "downloadSwitchBackup": "Pobierz backup", + "httpStatus": "Status HTTP", + "serverHeader": "Nagłówek Server", + "authMode": "Tryb autoryzacji", + "backupEndpoint": "Endpoint backupu", + "backupAvailable": "Dostępny", + "backupUnavailable": "Niedostępny", + "connectionSectionTitle": "Profil połączenia", + "connectionSectionHint": "Podstawowa tożsamość urządzenia i endpoint używany do połączenia.", + "credentialsSectionTitle": "Dostęp i poświadczenia", + "routerDialogSubtitle": "Ustaw adres urządzenia, dane dostępu SSH i preferowaną metodę logowania.", + "switchDialogSubtitle": "Ustaw endpoint SwitchOS i opcjonalne poświadczenia lokalne lub domyślne z ustawień." + }, + "files": { + "title": "Repozytorium", + "eyebrow": "repozytorium artefaktów", + "subtitle": "Szukaj, porównuj i udostępniaj backupy z jednego czytelnego widoku.", + "downloadZip": "Pobierz ZIP", + "visibleFiles": "Widoczne pliki", + "visibleFilesHint": "Wynik bieżącego filtra", + "liveTag": "Live", + "selected": "Zaznaczone", + "selectedHint": "Gotowe do akcji zbiorczych", + "batchTag": "Batch", + "exportsCard": "Eksporty", + "exportsHint": "Snapshoty konfiguracji", + "binaryCard": "Backupy binarne", + "binaryHint": "Obrazy odzyskiwania", + "filtersTitle": "Filtry", + "filtersSubtitle": "Zawęź listę plików po routerze, typie lub słowie kluczowym.", + "searchLabel": "Szukaj", + "searchPlaceholder": "Szukaj po pliku lub routerze", + "typeLabel": "Typ", + "routerLabel": "Urządzenie", + "dateLabel": "Data", + "datePlaceholder": "Wybierz datę", + "sortLabel": "Sortowanie", + "orderLabel": "Kolejność", + "allTypes": "Wszystkie typy", + "allRouters": "Wszystkie urządzenia", + "sortNewest": "Najnowsze", + "sortName": "Nazwa", + "sortRouter": "Urządzenie", + "sortType": "Typ", + "tableTitle": "Tabela repozytorium", + "tableSubtitle": "Artefakty dostępne do pobrania, wysyłki e-mail i przywracania.", + "compareHint": "Zaznacz dokładnie dwa pliki .rsc, aby je porównać.", + "compareSelected": "Porównaj zaznaczone eksporty", + "fileColumn": "Plik", + "typeColumn": "Typ", + "routerColumn": "Urządzenie", + "createdColumn": "Utworzono", + "actionsColumn": "Akcje", + "checksum": "Checksum", + "exportType": "Eksport", + "binaryType": "Backup binarny", + "previewDialogTitle": "Podgląd eksportu", + "diffDialogTitle": "Diff eksportów", + "openHtmlDiff": "Otwórz HTML diff", + "sizeColumn": "Rozmiar", + "compareColumn": "Porównanie", + "compareOlder": "Starszy plik", + "compareNewer": "Nowszy plik", + "pickOlder": "Wybierz starszy backup", + "pickNewer": "Wybierz nowszy backup", + "compareLatestPair": "Najnowsza para", + "setOlder": "Ustaw jako starszy", + "setNewer": "Ustaw jako nowszy", + "latestForRouter": "Diff dla routera", + "binaryNoCompare": "Diff tylko dla .rsc", + "openPlainDiff": "Pokaż diff tekstowy", + "minutesAgo": "{{value}} min temu", + "hoursAgo": "{{value}} godz. temu", + "daysAgo": "{{value}} dni temu", + "compareTitle": "Porównanie eksportów", + "compareSubtitle": "Wybierz dwa pliki .rsc i uruchom diff bez przewijania całej tabeli.", + "exportPoolLabel": "eksportów gotowych do porównania", + "compareSelectionHint": "Wybierz starszy i nowszy plik", + "compareReadySameRouter": "Para gotowa · router {{router}}", + "compareReadyMixedRouters": "Para gotowa · różne routery" + }, + "settings": { + "title": "Ustawienia", + "eyebrow": "konfiguracja platformy", + "subtitle": "Skonfiguruj harmonogramy, retencję, powiadomienia, testy połączeń i współdzielone dane SSH.", + "testEmail": "Test e-mail", + "testPushover": "Test Pushover", + "retentionTitle": "Retencja", + "retentionSubtitle": "Okna automatycznego czyszczenia plików i logów.", + "backupRetentionDays": "Dni retencji backupów", + "logRetentionDays": "Dni retencji logów", + "retentionCron": "Cron retencji", + "automationTitle": "Automatyzacja", + "automationSubtitle": "Harmonogramy eksportów, backupów binarnych, retencji i testów połączeń.", + "enableAutoExport": "Włącz auto eksport", + "enableAutoExportHint": "Uruchamiaj zadania eksportu według reguł cron poniżej.", + "exportCron": "Cron eksportu", + "binaryCron": "Cron backupu binarnego", + "notificationsTitle": "Powiadomienia", + "notificationsSubtitle": "Konfiguracja dostarczania SMTP i Pushover.", + "smtpEnabled": "Włącz SMTP", + "smtpEnabledHint": "Wysyłaj powiadomienia przez bramkę SMTP.", + "failuresOnly": "Tylko błędy", + "failuresOnlyHint": "Ogranicz alerty do nieudanych zadań.", + "smtpHost": "Host SMTP", + "smtpPort": "Port SMTP", + "smtpLogin": "Login SMTP", + "smtpPassword": "Hasło SMTP", + "recipientEmail": "E-mail odbiorcy", + "pushoverToken": "Token Pushover", + "pushoverUserKey": "Klucz użytkownika Pushover", + "pushoverTokenPlaceholder": "Token aplikacji", + "pushoverUserKeyPlaceholder": "Klucz użytkownika", + "sshDefaultsTitle": "Domyślne Poświadczenia", + "sshDefaultsSubtitle": "Wspólny klucz SSH oraz domyślne logowanie SwitchOS używane przez urządzenia.", + "globalSshPrivateKey": "Globalny klucz prywatny SSH", + "globalSshPrivateKeyPlaceholder": "Wklej klucz prywatny PEM lub OpenSSH", + "save": "Zapisz ustawienia", + "scheduleDisabled": "Wyłączony", + "scheduleDaily": "Codziennie", + "scheduleWeekly": "Co tydzień", + "scheduleCustom": "Własny cron", + "scheduleMode": "Tryb harmonogramu", + "scheduleTime": "Godzina", + "scheduleWeekday": "Dzień tygodnia", + "weekdayMonday": "Poniedziałek", + "weekdayTuesday": "Wtorek", + "weekdayWednesday": "Środa", + "weekdayThursday": "Czwartek", + "weekdayFriday": "Piątek", + "weekdaySaturday": "Sobota", + "weekdaySunday": "Niedziela", + "scheduleDisabledHint": "Zadanie nie będzie uruchamiane automatycznie.", + "scheduleDailySummary": "Codziennie o {{time}}", + "scheduleWeeklySummary": "Co {{weekday}} o {{time}}", + "scheduleCustomEmpty": "Wpisz własny cron", + "statusEnabled": "Aktywny", + "statusDisabled": "Wyłączony", + "noNextRun": "Brak zaplanowanego uruchomienia", + "exportScheduleTitle": "Eksporty tekstowe", + "binaryScheduleTitle": "Backupy binarne", + "automationPlannerTitle": "Planer zadań", + "automationPlannerSubtitle": "Każde zadanie ma osobny harmonogram, więc możesz osobno ustawić eksport, backup binarny i retencję.", + "automationPlannerTag": "Elastyczne okna", + "exportPlannerHint": "Ustaw kiedy mają powstawać czytelne eksporty tekstowe. Tryb Wyłączony całkowicie zatrzymuje automat.", + "binaryPlannerHint": "Oddzielne okno dla pełnych backupów binarnych, gdy potrzebujesz punktów odtworzenia.", + "retentionPlannerHint": "Retencja czyści stare backupy i logi według osobnego planu.", + "connectionTestsTitle": "Automatyczne testy połączeń", + "connectionTestsHint": "Aplikacja może sama odświeżać status urządzeń. Ustaw 0, aby wyłączyć automatyczne testy.", + "connectionTestIntervalMinutes": "Test co X minut", + "connectionTestsEverySummary": "Co {{minutes}} min", + "connectionTestsDisabledHint": "Automatyczne testy połączeń są wyłączone.", + "sshKeyHelper": "Wspólny klucz SSH jest po prawej stronie. Podejrzenie wymaga potwierdzenia hasłem do konta.", + "sshKeyStoredTag": "Klucz zapisany", + "sshKeyWillBeRemovedTag": "Do usunięcia", + "sshRevealHint": "Bieżący klucz pozostaje ukryty, dopóki nie potwierdzisz hasła. Możesz też wkleić nowy klucz poniżej, aby go podmienić.", + "revealSshPassword": "Aktualne hasło do konta", + "revealSshPasswordPlaceholder": "Wpisz hasło, aby podejrzeć klucz", + "revealSshKey": "Pokaż klucz", + "hideSshKey": "Ukryj klucz", + "clearSshKey": "Wyczyść klucz", + "sshKeyClearNotice": "Zapisany wspólny klucz SSH zostanie usunięty po zapisaniu zmian.", + "globalSshPrivateKeyHiddenPlaceholder": "Zapisany klucz jest ukryty. Wpisz hasło powyżej, aby go zobaczyć, albo wklej tutaj nowy klucz, aby go podmienić.", + "sshRevealPasswordRequired": "Wpisz aktualne hasło, aby podejrzeć klucz SSH.", + "sshRevealPasswordInvalid": "Hasło użyte do podejrzenia klucza SSH jest nieprawidłowe.", + "schedulerAutoExportLabel": "Automatyczne eksporty", + "schedulerBinaryLabel": "Backupy binarne", + "schedulerRetentionLabel": "Czyszczenie retencji", + "schedulerConnectionLabel": "Testy połączeń", + "schedulerLogsLabel": "Czyszczenie logów", + "schedulerLogsDescription": "Co 24 godziny", + "schedulerCronDescription": "{{description}}", + "schedulerInvalidCron": "Nieprawidłowe wyrażenie cron", + "interfaceTitle": "Konfiguracja interfejsu", + "interfaceSubtitle": "Preferencje języka i typografii zapisywane dla Twojego konta.", + "interfacePreferencesTitle": "Wygląd przestrzeni roboczej", + "interfacePreferencesHint": "Wybierz domyślny język i rodzinę fontów dla całej aplikacji.", + "interfacePreferencesTag": "Per-user", + "fontFamily": "Rodzina fontów", + "fontDefault": "Domyślna", + "switchosDefaultsTitle": "Domyślne dane SwitchOS", + "switchosDefaultsHint": "Używane, gdy urządzenie SwitchOS nie ma własnego loginu lub hasła.", + "defaultSwitchosUsername": "Domyślny użytkownik SwitchOS", + "defaultSwitchosPassword": "Domyślne hasło SwitchOS" + }, + "logs": { + "title": "Logi", + "eyebrow": "historia operacyjna", + "subtitle": "Przeglądaj ostatnie zdarzenia eksportu, przywracania i utrzymania.", + "daysPlaceholder": "dni", + "deleteOlderThan": "Usuń starsze niż", + "entriesLabel": "Wpisy", + "entriesHint": "Załadowane rekordy", + "auditTag": "Audyt", + "retentionLabel": "Retencja", + "retentionHint": "Próg czyszczenia", + "policyTag": "Polityka", + "daysSuffix": "dni", + "tableTitle": "Tabela logów", + "tableSubtitle": "Chronologiczna lista operacji zapisanych przez backend.", + "timestampColumn": "Czas", + "messageColumn": "Komunikat", + "retentionInfoLabel": "Ustawiona retencja logów" + }, + "toast": { + "success": "Gotowe", + "info": "Informacja", + "error": "Błąd", + "exportPreviewLoaded": "Załadowano podgląd eksportu.", + "backupSentEmail": "Backup został wysłany e-mailem.", + "binaryUploaded": "Backup binarny został wysłany na router.", + "backupDeleted": "Backup został usunięty.", + "selectedBackupsDeleted": "Wybrane backupy zostały usunięte.", + "diffLoaded": "Załadowano diff.", + "archivePrepared": "Archiwum zostało przygotowane.", + "exportedRouters": "Wykonano eksport dla {{count}} urządzeń.", + "binaryCompletedRouters": "Wykonano backup binarny dla {{count}} urządzeń.", + "routerCreated": "Router został dodany.", + "routerUpdated": "Router został zaktualizowany.", + "routerDeleted": "Router został usunięty.", + "exportCreated": "Eksport został utworzony.", + "binaryCreated": "Backup binarny został utworzony.", + "connectionSuccessful": "Połączenie zakończone powodzeniem.", + "settingsSaved": "Ustawienia zostały zapisane.", + "testEmailSent": "Wysłano testowy e-mail.", + "testPushoverSent": "Wysłano testowe powiadomienie Pushover.", + "logsDeletedOlderThan": "Usunięto logi starsze niż {{days}} dni.", + "passwordChanged": "Hasło zostało zmienione.", + "connectionFailed": "Test połączenia nie powiódł się.", + "sshKeyUnlocked": "Klucz SSH został odblokowany.", + "settingsSaveFailed": "Nie udało się zapisać ustawień.", + "testEmailFailed": "Nie udało się wysłać testowego e-maila.", + "testPushoverFailed": "Nie udało się wysłać testowego powiadomienia Pushover.", + "swosBetaProbeOk": "Połączenie ze SwitchOS zostało sprawdzone.", + "swosBetaProbeFailed": "Nie udało się sprawdzić dostępu do SwitchOS.", + "swosBetaDownloadOk": "Backup SwitchOS został pobrany.", + "swosBetaDownloadFailed": "Nie udało się pobrać backupu SwitchOS." + }, + "confirm": { + "header": "Potwierdzenie", + "deleteBackup": "Usunąć ten plik backupu?", + "deleteSelectedFiles": "Usunąć {{count}} zaznaczonych plików?", + "deleteRouterWithFiles": "Usunąć router i wszystkie powiązane pliki?", + "deleteLogsOlderThan": "Usunąć logi starsze niż {{days}} dni?" + }, + "footer": { + "authorLabel": "Autor", + "apiLabel": "API", + "apiOnline": "online", + "apiOffline": "offline", + "apiChecking": "sprawdzanie", + "apiLatencyLabel": "Odpowiedź API", + "apiDocs": "Docs API", + "apiOfflineTitle": "Brak połączenia z API", + "apiOfflineMessage": "Backend nie odpowiada. Część funkcji może być chwilowo niedostępna.", + "retry": "Ponów" + }, + "diffConfigs": { + "title": "Diff konfiguracji", + "eyebrow": "porównanie eksportów", + "subtitle": "Dedykowany widok do wygodnego porównywania konfiguracji RouterOS.", + "exportsCard": "Eksporty do diffu", + "exportsCardHint": "Pliki .rsc w bieżącym zakresie", + "scopeCard": "Zakres", + "scopeCardHint": "Wybrany router lub cała flota", + "scopeTag": "Zakres", + "readyCard": "Para", + "readyCardHint": "Stan wyboru do porównania", + "readyTag": "Stan", + "lastDiffCard": "Ostatni diff", + "lastDiffCardHint": "Ostatnio otwarta para plików", + "lastDiffTag": "Historia", + "workspaceTitle": "Stanowisko porównawcze", + "workspaceSubtitle": "Wybierz router, ustaw starszy i nowszy eksport, a potem otwórz diff w modalu.", + "tableTitle": "Eksporty do wyboru", + "tableSubtitle": "Szybkie przypisanie starszego i nowszego pliku oraz podgląd bez opuszczania strony.", + "waitingTag": "Czeka", + "noneSelected": "Brak" + }, + "switchosBeta": { + "title": "SwitchOS beta", + "eyebrow": "switchos / wersja beta", + "subtitle": "Osobny moduł do pobierania kopii urządzeń SwitchOS bez integracji z głównym repozytorium.", + "betaTag": "Nietestowane beta", + "summaryStandaloneValue": "Osobno", + "summaryStandaloneLabel": "Działa poza głównym obiegiem", + "summaryProtocolLabel": "Protokół docelowy", + "summaryArtifactLabel": "Format kopii", + "warningTitle": "Status modułu", + "warningSubtitle": "To osobna ścieżka robocza przygotowana pod scraping WWW dla SwitchOS.", + "warningHeadline": "Ta zakładka jest oznaczona jako nietestowana wersja beta.", + "warningBody": "Nie zapisuje urządzeń ani plików do istniejącej listy RouterOS. Służy do ręcznego sprawdzenia dostępu i pobrania pliku backupu SwitchOS.", + "formTitle": "Dane urządzenia", + "formSubtitle": "Wprowadź adres przełącznika i dane logowania do panelu WWW.", + "label": "Etykieta pliku", + "labelPlaceholder": "np. css326-magazyn", + "host": "Host / URL", + "hostPlaceholder": "np. 192.168.88.1 albo http://192.168.88.1", + "port": "Port", + "username": "Użytkownik", + "password": "Hasło", + "passwordPlaceholder": "Puste, jeśli urządzenie nie ma hasła", + "probeButton": "Sprawdź dostęp", + "downloadButton": "Pobierz backup .swb", + "resultTitle": "Wynik połączenia", + "resultSubtitle": "Podgląd odpowiedzi urządzenia przed pobraniem pliku.", + "resultEmpty": "Najpierw sprawdź dostęp do urządzenia albo od razu pobierz backup.", + "baseUrl": "Adres bazowy", + "httpStatus": "Kod HTTP", + "authMode": "Tryb autoryzacji", + "pageTitle": "Tytuł strony", + "serverHeader": "Nagłówek serwera", + "backupEndpoint": "Endpoint backupu", + "available": "Dostępny", + "unavailable": "Niedostępny", + "genericError": "Nie udało się wykonać operacji SwitchOS beta." + } +} diff --git a/frontend/src/favicon.ico b/frontend/src/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/index.html b/frontend/src/index.html new file mode 100644 index 0000000..ecb3cae --- /dev/null +++ b/frontend/src/index.html @@ -0,0 +1,22 @@ + + + + + Mikrotik Backup System + + + + + + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..e99af6f --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,40 @@ +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { ConfirmationService, MessageService } from 'primeng/api'; +import { providePrimeNG } from 'primeng/config'; +import AppPreset from './app/core/theme-preset'; +import { provideRouter } from '@angular/router'; +import { provideTranslateService } from '@ngx-translate/core'; +import { provideTranslateHttpLoader } from '@ngx-translate/http-loader'; + +import { AppComponent } from './app/app.component'; +import { routes } from './app/app.routes'; +import { authInterceptor } from './app/core/interceptors/auth.interceptor'; + +bootstrapApplication(AppComponent, { + providers: [ + provideAnimations(), + provideHttpClient(withInterceptors([authInterceptor])), + provideRouter(routes), + providePrimeNG({ + theme: { + preset: AppPreset, + options: { + darkModeSelector: '.dark-theme', + cssLayer: false + } + } + }), + provideTranslateService({ + loader: provideTranslateHttpLoader({ + prefix: './assets/i18n/', + suffix: '.json' + }), + lang: 'en', + fallbackLang: 'en' + }), + MessageService, + ConfirmationService + ] +}).catch((err) => console.error(err)); diff --git a/frontend/src/styles.css b/frontend/src/styles.css new file mode 100644 index 0000000..5b12ce5 --- /dev/null +++ b/frontend/src/styles.css @@ -0,0 +1,4 @@ +@import './styles/pages.css'; +@import './styles/layout.css'; +@import './styles/auth.css'; +@import './styles/dashboard.css'; diff --git a/frontend/src/styles/auth.css b/frontend/src/styles/auth.css new file mode 100644 index 0000000..42bccf7 --- /dev/null +++ b/frontend/src/styles/auth.css @@ -0,0 +1,165 @@ +.auth-toolbar { + position: fixed; + top: 1rem; + right: 1rem; + display: inline-flex; + align-items: center; + gap: 0.45rem; + z-index: 130; +} + +.auth-toolbar__btn.p-button { + min-width: 2.6rem; + height: 2.4rem; + padding: 0.35rem 0.65rem; + border-color: var(--border-color); + background: color-mix(in srgb, var(--surface-1) 90%, transparent); + color: var(--text-main); +} + +.app-auth-view { + min-height: 100vh; + display: grid; + grid-template-rows: minmax(0, 1fr) auto; +} + +.app-auth-view__content { + min-height: 0; + display: grid; + place-items: center; + padding: 1.5rem; +} + +.app-auth-view__content > * { + width: 100%; + max-width: 720px; +} + +.auth-shell, +.auth-shell--login, +.auth-shell--compact { + position: relative; + display: grid; + align-items: center; + justify-items: stretch; + width: 100%; + min-height: min(100%, 720px); + padding: clamp(4.5rem, 8vh, 5.5rem) 0 1rem; + margin: 0; +} + +.auth-card, +.app-auth-view__content .auth-card--wide { + width: 100%; + min-width: 0; + margin: 0 auto; + display: flex; + flex-direction: column; + justify-content: center; + gap: 1.2rem; + padding: 2rem; + border-radius: 24px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, var(--surface-1) 0%, var(--surface-0) 100%); + box-shadow: var(--shadow-md); +} + +.auth-card__header { + display: grid; + gap: 0.45rem; +} + +.auth-card__header h2 { + margin: 0; + font-size: 1.5rem; + line-height: 1.1; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.auth-card__header p { + margin: 0; + color: var(--text-soft); + font-size: 0.82rem; + line-height: 1.6; +} + +.auth-form { + display: grid; + gap: 1rem; +} + +.auth-form--grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.auth-form--grid > .form-field, +.auth-form--grid > small, +.auth-form--grid > div { + min-width: 0; +} + +.auth-card__actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.auth-card__actions > * { + min-width: 0; +} + +.auth-link { + font-size: 0.78rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--text-soft); + border-bottom: 1px solid transparent; +} + +.auth-link:hover { + color: var(--text-main); + border-bottom-color: currentColor; +} + +.p-button.auth-primary-btn { + width: 100%; + justify-content: center; +} + +.form-field--full { + grid-column: 1 / -1; +} + +.layout-footer--auth { + padding-top: 0.5rem; +} + +@media (max-width: 991px) { + .app-auth-view__content { + align-items: start; + padding: 1rem; + } + + .auth-shell, + .auth-shell--login, + .auth-shell--compact { + min-height: 0; + padding: 4.5rem 0 0.5rem; + } + + .auth-form--grid { + grid-template-columns: 1fr; + } + + .auth-card__actions--split { + flex-direction: column; + align-items: stretch; + } + + .auth-toolbar { + top: 0.9rem; + right: 0.9rem; + } +} diff --git a/frontend/src/styles/dashboard.css b/frontend/src/styles/dashboard.css new file mode 100644 index 0000000..0e26165 --- /dev/null +++ b/frontend/src/styles/dashboard.css @@ -0,0 +1,187 @@ +.dashboard-focus-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1.25rem; + margin-top: 1.5rem; +} + +.dashboard-focus-block { + display: grid; + gap: 1.25rem; +} + +.dashboard-focus-block--pie { + grid-template-columns: minmax(220px, 260px) minmax(0, 1fr); + align-items: center; +} + +.dashboard-focus-pie { + width: min(100%, 240px); + aspect-ratio: 1; + border-radius: 50%; + display: grid; + place-items: center; + margin: 0 auto; +} + +.dashboard-focus-pie__inner { + width: 68%; + aspect-ratio: 1; + border-radius: 50%; + display: grid; + place-items: center; + gap: 0.25rem; + text-align: center; + padding: 1rem; + background: color-mix(in srgb, var(--surface-1) 96%, transparent); + border: 1px solid var(--border-color); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.dashboard-focus-pie__inner strong { + font-family: var(--font-title); + font-size: clamp(1.4rem, 2.6vw, 2rem); + letter-spacing: 0.05em; +} + +.dashboard-focus-pie__inner span { + color: var(--text-soft); + font-size: 0.78rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.dashboard-focus-summary { + display: grid; + gap: 0.75rem; +} + +.dashboard-focus-summary__lead { + margin: 0; + color: var(--text-soft); + line-height: 1.6; +} + +.dashboard-focus-metrics { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.75rem; +} + +.dashboard-focus-metric { + padding: 0.95rem 1rem; + border-radius: 16px; + border: 1px solid var(--border-color); + background: color-mix(in srgb, var(--surface-1) 92%, transparent); + display: grid; + gap: 0.35rem; +} + +.dashboard-focus-metric span { + color: var(--text-soft); + font-size: 0.76rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.dashboard-focus-metric strong { + font-family: var(--font-title); + font-size: 1rem; + letter-spacing: 0.05em; +} + +.dashboard-focus-activity { + display: grid; + gap: 1rem; +} + +.dashboard-focus-activity__summary { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; +} + +.dashboard-focus-activity__summary p { + margin: 0; + color: var(--text-soft); +} + +.dashboard-focus-activity__summary strong { + font-family: var(--font-title); + font-size: 1.5rem; + letter-spacing: 0.06em; +} + +.dashboard-focus-columns { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 0.85rem; + align-items: end; + min-height: 220px; +} + +.dashboard-focus-column { + display: grid; + gap: 0.55rem; + justify-items: center; +} + +.dashboard-focus-column small, +.dashboard-focus-column span { + color: var(--text-soft); + font-size: 0.76rem; +} + +.dashboard-focus-column strong { + font-family: var(--font-title); + font-size: 0.9rem; +} + +.dashboard-focus-column__track { + width: 100%; + min-height: 140px; + display: flex; + align-items: end; + justify-content: center; + padding: 0.6rem; + border-radius: 18px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, color-mix(in srgb, var(--surface-1) 90%, transparent), color-mix(in srgb, var(--surface-0) 94%, transparent)); +} + +.dashboard-focus-column__track span { + width: min(36px, 100%); + border-radius: 999px; + background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 84%, white 8%), color-mix(in srgb, var(--blue) 82%, white 6%)); + min-height: 0; +} + +.dashboard-focus-empty { + min-height: 220px; + display: grid; + place-items: center; +} + +@media (max-width: 1100px) { + .dashboard-focus-grid, + .dashboard-focus-block--pie { + grid-template-columns: 1fr; + } +} + +@media (max-width: 720px) { + .dashboard-focus-metrics { + grid-template-columns: 1fr; + } + + .dashboard-focus-columns { + gap: 0.55rem; + } + + .dashboard-focus-column__track { + min-height: 120px; + padding: 0.45rem; + } +} diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css new file mode 100644 index 0000000..4ec88d1 --- /dev/null +++ b/frontend/src/styles/layout.css @@ -0,0 +1,195 @@ + +.layout-shell { + min-height: 100vh; + padding-left: var(--sidebar-width); + transition: padding-left 0.2s ease; +} + +.layout-shell--collapsed { + padding-left: var(--sidebar-collapsed-width); +} + +.layout-shell--collapsed .layout-sidebar { + width: var(--sidebar-collapsed-width); +} + +.layout-main { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.layout-sidebar { + position: fixed; + inset: 0 auto 0 0; + width: var(--sidebar-width); + z-index: 100; + transition: width 0.2s ease, transform 0.2s ease; +} + +.layout-sidebar app-sidebar { + height: 100%; + display: flex; + flex-direction: column; +} + +.layout-content { + flex: 1; + padding: 0 1.5rem 1.5rem; + min-width: 0; +} + +.layout-footer { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.85rem 1.2rem; + padding: 0 2rem 1.5rem; +} + +.layout-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + opacity: 0; + visibility: hidden; + z-index: 95; + transition: opacity 0.2s ease; +} + +.layout-overlay.is-visible { + opacity: 1; + visibility: visible; +} + +.topbar { + margin: 0; + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + position: sticky; + top: 0; + z-index: 90; + min-height: 68px; + padding: 0.85rem 1.5rem; + border-bottom: 1px solid rgba(17, 20, 23, 0.1); + backdrop-filter: blur(8px); + background: rgba(255, 255, 255, 0.12); +} + +body.dark-theme .topbar { + border-bottom-color: rgba(146, 170, 194, 0.12); + background: rgba(23, 33, 43, 0.74); +} + +.topbar__right { + display: flex; + align-items: center; + gap: 0.8rem; + min-width: 0; +} + +.topbar__lang-picker, +.auth-toolbar__select-wrap { + position: relative; +} + +.topbar__lang-picker::after, +.auth-toolbar__select-wrap::after { + content: '\e902'; + font-family: 'primeicons'; + position: absolute; + right: 1rem; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: var(--text-soft); + font-size: 0.8rem; +} + +.topbar__lang-select, +.auth-toolbar__select { + appearance: none; + -webkit-appearance: none; + width: 100%; + min-width: 10.5rem; + min-height: 2.95rem; + padding: 0.72rem 2.7rem 0.72rem 1rem; + border-radius: 14px; + border: 1px solid var(--border-strong); + background: color-mix(in srgb, var(--surface-1) 88%, white 12%); + color: var(--text-main); + font: inherit; + font-size: 0.96rem; + font-weight: 500; + line-height: 1.35; + cursor: pointer; + transition: border-color 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease; +} + +.topbar__lang-select:hover, +.auth-toolbar__select:hover { + border-color: color-mix(in srgb, var(--accent) 56%, var(--border-strong)); +} + +.topbar__lang-select:focus, +.auth-toolbar__select:focus { + outline: none; + border-color: color-mix(in srgb, var(--blue) 72%, var(--border-strong)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 18%, transparent); +} + +.topbar__lang-select option, +.auth-toolbar__select option { + color: var(--text-main); + background: var(--surface-1); +} + +body.dark-theme .topbar__lang-select, +body.dark-theme .auth-toolbar__select { + background: rgba(255, 255, 255, 0.03); + border-color: var(--border-color); +} + +body.dark-theme .topbar__lang-select option, +body.dark-theme .auth-toolbar__select option { + color: #d6e0e8; + background: #1c2631; +} + + +@media (max-width: 991px) { + .layout-shell, + .layout-shell--collapsed { + padding-left: 0; + } + + .layout-sidebar { + transform: translateX(-100%); + width: min(88vw, var(--sidebar-width)); + } + + .layout-sidebar.is-open { + transform: translateX(0); + } + + .topbar, + .layout-content, + .layout-footer { + padding-left: 1rem; + padding-right: 1rem; + } + + .topbar { + flex-direction: column; + align-items: flex-start; + } + + .topbar__right { + width: 100%; + justify-content: space-between; + flex-wrap: wrap; + } +} diff --git a/frontend/src/styles/pages.css b/frontend/src/styles/pages.css new file mode 100644 index 0000000..37c1769 --- /dev/null +++ b/frontend/src/styles/pages.css @@ -0,0 +1,3817 @@ +:root{ + --bg-page: #ecece8; + --bg-grid: radial-gradient(circle at 10% 10%, rgba(159, 115, 84, 0.08), transparent 24%), radial-gradient(circle at 85% 14%, rgba(19, 24, 28, 0.05), transparent 18%); + --surface-0: rgba(250, 250, 247, 0.82); + --surface-1: #f8f8f5; + --surface-2: #f1f1ed; + --surface-3: #dfdfd8; + --surface-4: #d0d0c8; + --text-main: #111417; + --text-soft: #5e666e; + --text-faint: #7b848d; + --border-color: rgba(17, 20, 23, 0.12); + --border-strong: rgba(17, 20, 23, 0.2); + --primary: #101316; + --primary-soft: #49535c; + --accent: #8d593a; + --accent-2: #0c6a50; + --blue: #285c9d; + --success: #0d8a63; + --warning: #b67a21; + --danger: #c24646; + --shadow-lg: 0 24px 60px rgba(17, 20, 23, 0.08); + --shadow-md: 0 12px 30px rgba(17, 20, 23, 0.05); + --radius-xl: 28px; + --radius-lg: 20px; + --radius-md: 14px; + --sidebar-width: 260px; + --sidebar-collapsed-width: 92px; + --topbar-height: 76px; + --font-body: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-title: "Roboto Mono", "IBM Plex Mono", "SFMono-Regular", Consolas, monospace; +} + +body.dark-theme{ + --bg-page: #17212b; + --bg-grid: linear-gradient(180deg, rgba(91, 119, 145, 0.05), transparent 32%), radial-gradient(circle at 18% 12%, rgba(63, 130, 196, 0.08), transparent 18%); + --surface-0: rgba(29, 39, 51, 0.9); + --surface-1: #1d2733; + --surface-2: #222d3a; + --surface-3: #2d3947; + --surface-4: #3a4858; + --text-main: #dae4ec; + --text-soft: #93a5b6; + --text-faint: #708295; + --border-color: rgba(146, 170, 194, 0.16); + --border-strong: rgba(146, 170, 194, 0.25); + --primary: #edf2f7; + --primary-soft: #bccada; + --accent: #4b90d9; + --accent-2: #4fb593; + --blue: #4b90d9; + --success: #4fb593; + --warning: #f0b45b; + --danger: #e37a7a; + --shadow-lg: 0 22px 48px rgba(0, 0, 0, 0.24); + --shadow-md: 0 10px 24px rgba(0, 0, 0, 0.18); +} + +*{ + box-sizing: border-box; +} + +html, body{ + min-height: 100%; +} + +body{ + margin: 0; + color: var(--text-main); + font-family: var(--font-body); + background: var(--bg-page); + background-image: var(--bg-grid); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body::before{ + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + background-image: linear-gradient(rgba(17, 20, 23, 0.018) 1px, transparent 1px), linear-gradient(90deg, rgba(17, 20, 23, 0.018) 1px, transparent 1px); + background-size: 28px 28px; + opacity: 0.28; +} + +body.dark-theme::before{ + background-image: linear-gradient(rgba(218, 228, 236, 0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(218, 228, 236, 0.02) 1px, transparent 1px); + opacity: 0.15; +} + +body, .p-component, .p-inputtext, .p-dropdown, .p-button, .p-card, .p-tag, .p-inputswitch, .p-table{ + font-family: var(--font-body); +} + +pre, code, .page-header__title, .dashboard-hero__title, .topbar__headline, .sidebar-brand__text h2, .stat-card__value, .stat-card__label, .section-card__title, .hero-spec strong, .hero-terminal__tabs, .runbook-item strong, .table-primary{ + font-family: var(--font-title); +} + +img{ + max-width: 100%; +} + +a{ + color: inherit; + text-decoration: none; +} + +small{ + color: var(--text-soft); +} + +.layout-shell{ + min-height: 100vh; + padding-left: var(--sidebar-width); + transition: padding-left 0.2s ease; +} + +.layout-shell--collapsed{ + padding-left: var(--sidebar-collapsed-width); +} + +.layout-sidebar{ + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + background: linear-gradient(180deg, #151d26 0%, #19222d 100%); + border-right: 1px solid rgba(255, 255, 255, 0.08); + color: #d7e1eb; + padding: 1.15rem 1rem; + z-index: 100; + transition: width 0.2s ease, transform 0.2s ease; + overflow: hidden; +} + +.layout-shell--collapsed .layout-sidebar{ + width: var(--sidebar-collapsed-width); +} + +.layout-main{ + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.layout-content{ + flex: 1; + padding: 0 2rem 2rem; +} + +.layout-footer{ + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.85rem 1.2rem; + padding: 0 2rem 1.5rem; + color: var(--text-faint); + font-size: 0.74rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.layout-overlay{ + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + opacity: 0; + visibility: hidden; + z-index: 95; + transition: opacity 0.2s ease; +} + +.layout-overlay.is-visible{ + opacity: 1; + visibility: visible; +} + +.topbar{ + min-height: var(--topbar-height); + margin: 0; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + border-bottom: 1px solid var(--border-color); + background: rgba(255, 255, 255, 0.18); + backdrop-filter: blur(10px); + position: sticky; + top: 0; + z-index: 90; +} + +body.dark-theme .topbar{ + background: rgba(23, 33, 43, 0.82); +} + +.topbar__left, .topbar__right, .header-actions-row, .table-actions, .inline-tags, .filters-actions, .dialog-actions, .hero-actions, .sidebar-brand, .sidebar-footer__card, .activity-item, .runbook-item, .topbar__user, .form-switch-row, .metrics-stack{ + display: flex; + align-items: center; + gap: 0.8rem; +} + +.topbar__left, .topbar__right, .header-actions-row, .table-actions, .inline-tags, .filters-actions, .dialog-actions, .topbar__caption, .page-header__eyebrow, .sidebar-section__label, .stat-card__label, .hero-spec span, .dashboard-hero__eyebrow, .hero-terminal small, .layout-footer span, .sidebar-footer__title{ + font-size: 0.72rem; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.topbar__caption, .page-header__eyebrow, .sidebar-section__label, .layout-footer, .sidebar-footer__text, .stat-card__hint, .section-card__subtitle, .page-header__subtitle, .dashboard-hero__text, .metric-tile span, .metric-row span, .activity-item__message + small, .table-secondary, .form-field label, .topbar__user-meta small{ + color: var(--text-soft); +} + +.topbar__headline{ + font-size: 1.02rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.topbar__status{ + display: inline-flex; + align-items: center; + gap: 0.55rem; + padding: 0.55rem 0.85rem; + border: 1px solid var(--border-color); + border-radius: 999px; + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.14em; + background: var(--surface-1); +} + +.status-dot, .activity-item__dot{ + width: 0.65rem; + height: 0.65rem; + border-radius: 999px; + background: var(--success); + box-shadow: 0 0 0 4px rgba(79, 181, 147, 0.12); +} + +.topbar__user-meta{ + display: flex; + flex-direction: column; + line-height: 1.2; +} + +.topbar__action-btn.p-button, .topbar__logout-btn.p-button, .topbar__menu-btn.p-button{ + min-width: auto; +} + +.sidebar-brand{ + align-items: center; + padding-bottom: 1.25rem; + margin-bottom: 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.sidebar-brand__logo{ + width: 42px; + height: 42px; + border: 1px solid rgba(255, 255, 255, 0.24); + border-radius: 12px; + display: grid; + place-items: center; + font-family: var(--font-title); + font-size: 0.86rem; + letter-spacing: 0.12em; + color: #fff; +} + +.sidebar-brand__text h2, .sidebar-brand__text p, .sidebar-section__label, .sidebar-footer__title, .sidebar-footer__text, .page-header__title, .page-header__subtitle, .section-card__title, .section-card__subtitle, .stat-card__label, .stat-card__value, .stat-card__hint, .metric-tile span, .metric-row span, .activity-item__message, .form-field label, .dashboard-hero__title, .dashboard-hero__text, .hero-terminal__tabs, .hero-spec strong, .hero-spec span{ + margin: 0; +} + +.sidebar-brand__text h2{ + font-size: 1.02rem; + color: #fff; + text-transform: lowercase; + letter-spacing: 0.18em; +} + +.sidebar-brand__text p, .sidebar-footer__text, .sidebar-section__label{ + color: rgba(215, 225, 235, 0.64); +} + +.sidebar-section{ + padding: 0.25rem 0 0.5rem; +} + +.sidebar-nav{ + display: grid; + gap: 0.35rem; +} + +.sidebar-nav__item{ + display: flex; + align-items: center; + gap: 0.85rem; + min-height: 44px; + padding: 0.75rem 0.85rem; + border-radius: 14px; + color: rgba(230, 238, 245, 0.84); + border: 1px solid transparent; + transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; +} + +.sidebar-nav__item:hover{ + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.08); +} + +.sidebar-nav__item.is-active{ + background: rgba(75, 144, 217, 0.18); + border-color: rgba(75, 144, 217, 0.42); + color: #fff; +} + +.sidebar-nav__item i{ + font-size: 1rem; + width: 1rem; +} + +.sidebar-footer{ + margin-top: auto; + padding-top: 1rem; +} + +.sidebar-footer__card{ + flex-direction: column; + align-items: flex-start; + padding: 1rem; + border-radius: 16px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); +} + + +.page-header{ + display: flex; + justify-content: space-between; + align-items: flex-end; + gap: 1rem; + padding: 2rem 0 1.25rem; + border-bottom: 1px solid var(--border-color); + margin-bottom: 1.5rem; +} + +.page-header__title{ + font-size: clamp(2rem, 4vw, 3.2rem); + line-height: 1; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.page-header__subtitle{ + max-width: 56rem; + margin-top: 0.9rem; + font-size: 0.98rem; + line-height: 1.7; +} + +.stats-grid, .dashboard-grid, .metric-grid-2, .filters-grid, .two-column-grid, .form-grid-2, .settings-grid{ + display: grid; + gap: 1rem; +} + +.stats-grid{ + grid-template-columns: repeat(4, minmax(0, 1fr)); + margin-bottom: 1rem; +} + +.compact-grid{ + margin-bottom: 1rem; +} + +.dashboard-grid, .two-column-grid, .settings-grid{ + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.metric-grid-2, .form-grid-2{ + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.filters-grid{ + grid-template-columns: 1.3fr repeat(4, minmax(0, 1fr)) auto; + align-items: end; +} + +.stacked-sections{ + display: grid; + gap: 1rem; +} + +.section-card.p-card, .stat-card.p-card{ + border-radius: 20px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, var(--surface-1) 0%, var(--surface-0) 100%); + box-shadow: var(--shadow-md); + overflow: hidden; +} + +.section-card .p-card-body, .stat-card .p-card-body{ + padding: 0; +} + +.section-card .p-card-content, .stat-card .p-card-content{ + padding: 1.2rem 1.25rem; +} + +.section-card__header{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: flex-start; + padding-bottom: 1rem; + margin-bottom: 1rem; + border-bottom: 1px solid var(--border-color); +} + +.section-card__title{ + font-size: 1.05rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.section-card__subtitle{ + margin-top: 0.35rem; + line-height: 1.6; +} + +.stat-card{ + position: relative; +} + +.stat-card::before{ + content: ""; + position: absolute; + left: 0; + right: 0; + top: 0; + height: 4px; + background: linear-gradient(90deg, var(--accent), var(--blue)); +} + +.stat-card__row{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: flex-start; + margin-bottom: 1rem; +} + +.stat-card__label{ + margin-bottom: 0.55rem; +} + +.stat-card__value{ + font-size: clamp(1.6rem, 3vw, 2.1rem); + line-height: 1; + letter-spacing: 0.04em; +} + +.stat-card__hint{ + margin-top: 0.6rem; + font-size: 0.92rem; +} + +.stat-card__icon{ + width: 48px; + height: 48px; + border-radius: 14px; + display: grid; + place-items: center; + border: 1px solid var(--border-color); + background: var(--surface-2); + font-size: 1.05rem; +} + +.icon-blue{ color: var(--blue); } +.icon-emerald{ color: var(--success); } +.icon-amber{ color: var(--warning); } +.icon-violet{ color: var(--accent); } + +body.dark-theme .settings-actions{ + display: flex; + flex-direction: column; +} + +body.dark-theme .metric-tile, .empty-state{ + justify-content: space-between; +} + +.metric-grid-2{ + margin-top: 0.2rem; +} + +.metric-tile{ + padding: 1rem; + border: 1px solid var(--border-color); + border-radius: 16px; + background: var(--surface-2); +} + +.metric-tile strong, .metric-row strong{ + font-family: var(--font-title); + font-size: 1rem; + letter-spacing: 0.06em; +} + +.table-primary{ + font-size: 0.96rem; + letter-spacing: 0.04em; +} + +.code-preview{ + margin: 0; + padding: 1rem; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.06); + background: #17212b; + color: #dae4ec; + overflow: auto; + line-height: 1.6; + font-size: 0.86rem; +} + +.form-field{ + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.form-field label{ + font-size: 0.8rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.form-field--full{ + grid-column: 1 / -1; +} + +.form-switch-row{ + align-items: center; + min-height: 48px; + padding: 0.35rem 0; +} + +.form-error, .form-success{ + font-size: 0.88rem; +} + +.form-error{ color: var(--danger); } +.form-success{ color: var(--success); } + +.p-button{ + border-radius: 12px; + border-width: 1px; + font-size: 0.82rem; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + padding: 0.8rem 1rem; + background: var(--primary); + border-color: var(--primary); + color: #fff; + box-shadow: none; +} + +.p-button:not(.p-disabled):hover{ + filter: brightness(1.06); +} + +.p-button.p-button-secondary, .p-button.p-button-help, .p-button.p-button-outlined, .p-button.p-button-text{ + background: transparent; + color: var(--text-main); + border-color: var(--border-strong); +} + +.p-button.p-button-text{ + border-color: transparent; +} + +body.dark-theme .p-button.p-button-secondary, body.dark-theme .p-button.p-button-help, body.dark-theme .p-button.p-button-outlined, body.dark-theme .p-button.p-button-text{ + color: var(--text-main); +} + +.p-button.p-button-danger{ + background: transparent; + border-color: rgba(194, 70, 70, 0.5); + color: var(--danger); +} + +.p-inputtext, .p-dropdown, .p-inputtextarea, textarea.p-inputtextarea{ + width: 100%; + border-radius: 12px; + border: 1px solid var(--border-strong); + background: rgba(255, 255, 255, 0.5); + color: var(--text-main); + padding: 0.82rem 0.9rem; + box-shadow: none; +} + +body.dark-theme .p-inputtext, body.dark-theme .p-dropdown, body.dark-theme .p-inputtextarea, body.dark-theme textarea.p-inputtextarea{ + background: rgba(255, 255, 255, 0.03); +} + +.p-dropdown .p-dropdown-label{ + padding: 0; +} + +.p-dropdown .p-dropdown-trigger{ + width: 2.6rem; +} + +.p-inputtext:enabled:focus, .p-dropdown:not(.p-disabled).p-focus, .p-inputtextarea:enabled:focus, textarea.p-inputtextarea:enabled:focus{ + border-color: var(--accent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); +} + +.p-input-icon-left{ + position: relative; + display: block; +} + +.p-input-icon-left > i{ + left: 0.9rem; + color: var(--text-soft); +} + +.p-input-icon-left > .p-inputtext{ + padding-left: 2.45rem; +} + +.p-inputswitch{ + width: 3.5rem; + height: 1.8rem; +} + +.p-inputswitch .p-inputswitch-slider{ + background: var(--surface-3); + border: 1px solid var(--border-strong); + border-radius: 999px; +} + +.p-inputswitch .p-inputswitch-slider::before{ + width: 1.2rem; + height: 1.2rem; + left: 0.22rem; + margin-top: -0.6rem; + border-radius: 50%; + background: var(--surface-1); + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.18); +} + +.p-inputswitch.p-inputswitch-checked .p-inputswitch-slider{ + background: color-mix(in srgb, var(--accent) 80%, #fff 20%); +} + +body.dark-theme .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider{ + background: var(--blue); +} + +.p-tag{ + border-radius: 999px; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.p-progressbar{ + height: 0.65rem; + border-radius: 999px; + background: var(--surface-3); +} + +.p-progressbar .p-progressbar-value{ + background: linear-gradient(90deg, var(--accent), var(--blue)); +} + +.app-table{ + border: 1px solid var(--border-color); + border-radius: 18px; + overflow: hidden; +} + +.app-table .p-datatable-wrapper{ + border-radius: 18px; +} + +.app-table .p-datatable-table{ + min-width: 100%; +} + +.app-table .p-datatable-thead > tr > th{ + background: var(--surface-2); + color: var(--text-soft); + border-bottom: 1px solid var(--border-color); + font-size: 0.76rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + padding: 0.95rem 1rem; +} + +.app-table .p-datatable-tbody > tr{ + background: transparent; +} + +.app-table .p-datatable-tbody > tr > td{ + padding: 0.95rem 1rem; + border-bottom: 1px solid var(--border-color); +} + +.app-table .p-datatable-tbody > tr:hover > td{ + background: rgba(255, 255, 255, 0.35); +} + +body.dark-theme .app-table .p-datatable-thead > tr > th{ + background: #202a36; +} + +body.dark-theme .app-table .p-datatable-tbody > tr:hover > td{ + background: rgba(255, 255, 255, 0.03); +} + +.table-secondary{ + display: inline-block; + margin-top: 0.3rem; +} + +.p-checkbox .p-checkbox-box{ + border-radius: 8px; + border-color: var(--border-strong); + background: transparent; +} + +.p-checkbox:not(.p-checkbox-disabled) .p-checkbox-box:hover, .p-checkbox .p-checkbox-box.p-highlight{ + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 84%, #fff 16%); +} + +.p-paginator{ + border-top: 1px solid var(--border-color); + background: var(--surface-1); +} + +.p-dropdown-panel, .p-component-overlay{ + backdrop-filter: blur(10px); +} + +.p-dropdown-panel .p-dropdown-items{ + padding: 0.45rem; +} + +.p-dropdown-panel .p-dropdown-item{ + border-radius: 10px; +} + +.p-avatar{ + background: var(--primary); + color: #fff; + font-family: var(--font-title); +} + + +.p-chip{ + background: rgba(255, 255, 255, 0.08); + color: inherit; + border: 1px solid rgba(255, 255, 255, 0.12); +} + +.p-dialog{ + border-radius: 20px; + overflow: hidden; + border: 1px solid var(--border-color); +} + +.p-dialog .p-dialog-header, .p-dialog .p-dialog-content, .p-dialog .p-dialog-footer{ + background: var(--surface-1); + color: var(--text-main); +} + +.p-dialog .p-dialog-header{ + border-bottom: 1px solid var(--border-color); +} + +.p-dialog .p-dialog-footer{ + border-top: 1px solid var(--border-color); +} + +.p-dropdown-panel, .p-multiselect-panel{ + background: var(--surface-1); + border: 1px solid var(--border-color); + color: var(--text-main); +} + +.p-dropdown-panel .p-dropdown-item.p-highlight, .p-dropdown-panel .p-dropdown-item:hover{ + background: var(--surface-2); + color: var(--text-main); +} + +.empty-state, .compact-empty{ + padding: 1.2rem; + border: 1px dashed var(--border-strong); + border-radius: 14px; + color: var(--text-soft); + background: var(--surface-2); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +.settings-actions{ + justify-content: flex-end; + grid-column: 1 / -1; +} + +@media (max-width: 1300px) { + .stats-grid{ + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .filters-grid{ + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .dashboard-grid{ + grid-template-columns: 1fr; + } +} + +@media (max-width: 991px) { + .layout-shell, .layout-shell--collapsed{ + padding-left: 0; + } + + .layout-sidebar{ + transform: translateX(-100%); + width: min(88vw, var(--sidebar-width)); + } + + .layout-sidebar.is-open{ + transform: translateX(0); + } + + .topbar, .layout-content, .layout-footer{ + padding-left: 1rem; + padding-right: 1rem; + } + + .page-header{ + align-items: flex-start; + } +} + +@media (max-width: 720px) { + .page-header, .topbar, .filters-grid, .stats-grid, .metric-grid-2, .form-grid-2{ + grid-template-columns: 1fr; + } + + .topbar, .page-header{ + flex-direction: column; + align-items: flex-start; + } + + .topbar__right{ + width: 100%; + } + + + + + +} + +/* v6 refinements */ +.layout-content{ + padding-bottom: 2.5rem; +} + +.topbar{ + min-height: 72px; + padding-top: 0.9rem; + padding-bottom: 0.9rem; + background: rgba(249, 249, 246, 0.86); + border-bottom-color: rgba(17, 20, 23, 0.1); +} + +body.dark-theme .topbar{ + background: rgba(21, 29, 38, 0.92); + border-bottom-color: rgba(146, 170, 194, 0.12); +} + +.topbar__caption{ + letter-spacing: 0.18em; +} + +.topbar__headline{ + font-family: var(--font-title); + font-size: 1rem; + letter-spacing: 0.1em; +} + +.topbar__icon-btn.p-button, .topbar__logout-btn.p-button{ + min-width: 2.75rem; + height: 2.75rem; +} + +.topbar__icon-btn.p-button{ + border: 1px solid var(--border-color); + background: var(--surface-1); +} + +body.dark-theme .topbar__icon-btn.p-button{ + background: rgba(255, 255, 255, 0.03); +} + +.topbar__logout-btn.p-button{ + padding-inline: 1rem; +} + +.sidebar-brand__logo{ + border-radius: 12px; + background: rgba(255, 255, 255, 0.04); +} + +.sidebar-brand__text h2{ + font-size: 0.96rem; + text-transform: uppercase; +} + +.sidebar-brand__text p{ + color: rgba(215, 225, 235, 0.64); +} + +.p-button{ + min-height: 2.8rem; + border-radius: 10px; + border-width: 1px; + letter-spacing: 0.12em; + transition: transform 0.12s ease, filter 0.12s ease, border-color 0.12s ease, background-color 0.12s ease; +} + +.p-button:not(.p-disabled):hover{ + transform: translateY(-1px); +} + +.p-button.p-button-secondary, .p-button.p-button-help, .p-button.p-button-outlined, .p-button.p-button-text{ + background: var(--surface-1); +} + +body.dark-theme .p-button.p-button-secondary, body.dark-theme .p-button.p-button-help, body.dark-theme .p-button.p-button-outlined, body.dark-theme .p-button.p-button-text{ + background: rgba(255, 255, 255, 0.03); +} + +.p-button.p-button-text{ + background: transparent; +} + +.p-button.table-action-btn{ + width: 2.35rem; + min-width: 2.35rem; + height: 2.35rem; + padding: 0; + border-radius: 10px; +} + + + +.header-number-input{ + max-width: 6rem; +} + +.settings-toggle-grid{ + display: grid; + gap: 0.85rem; + margin-bottom: 1rem; +} + +.settings-toggle{ + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.95rem 1rem; + border: 1px solid var(--border-color); + border-radius: 16px; + background: color-mix(in srgb, var(--surface-1) 86%, transparent); +} + +.settings-toggle strong, .settings-toggle small{ + display: block; +} + +.settings-toggle strong{ + font-family: var(--font-title); + font-size: 0.9rem; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.settings-toggle small{ + margin-top: 0.3rem; + color: var(--text-soft); + line-height: 1.5; +} + +.settings-toggle--full{ + grid-column: 1 / -1; +} + +.p-inputswitch{ + width: 3.7rem; + height: 1.95rem; +} + +.p-inputswitch .p-inputswitch-slider{ + background: var(--surface-3); + box-shadow: inset 0 0 0 1px var(--border-strong); +} + +.p-inputswitch .p-inputswitch-slider::before{ + width: 1.32rem; + height: 1.32rem; + left: 0.2rem; + margin-top: -0.66rem; + background: #fff; +} + +body.dark-theme .p-inputswitch .p-inputswitch-slider::before{ + background: #dce5ee; +} + +.p-inputswitch.p-inputswitch-checked .p-inputswitch-slider{ + background: linear-gradient(90deg, var(--accent), #d38d5a); +} + +body.dark-theme .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider{ + background: linear-gradient(90deg, #356fae, #4b90d9); +} + + + + + + + + + + + + + + + + + + + + + +@media (max-width: 991px) { + .topbar__right{ + justify-content: space-between; + } + + +} + +/* v7 subtle polish */ +html{ + font-size: 14px; +} + +body{ + font-size: 0.95rem; +} + +body::before{ + opacity: 0.14; +} + +body.dark-theme{ + --surface-0: rgba(28, 38, 49, 0.94); + --surface-1: #1c2631; + --surface-2: #202b36; + --surface-3: #2a3642; + --surface-4: #364353; + --border-color: rgba(160, 183, 206, 0.12); + --border-strong: rgba(160, 183, 206, 0.18); + --text-main: #d6e0e8; + --text-soft: #8ea1b2; + --text-faint: #748696; + --shadow-lg: 0 18px 38px rgba(0, 0, 0, 0.18); + --shadow-md: 0 8px 20px rgba(0, 0, 0, 0.14); +} + +.topbar{ + min-height: 64px; + padding: 0.8rem 1.5rem; + background: rgba(248, 248, 245, 0.74); + backdrop-filter: blur(8px); +} + +body.dark-theme .topbar{ + background: rgba(19, 27, 35, 0.88); +} + +.topbar__caption, .page-header__eyebrow, .sidebar-section__label, .stat-card__label, .hero-spec span, .dashboard-hero__eyebrow, .hero-terminal small, .layout-footer span, .sidebar-footer__title{ + font-size: 0.68rem; + letter-spacing: 0.14em; +} + +.topbar__headline{ + font-size: 0.94rem; + letter-spacing: 0.08em; +} + +.topbar__status{ + padding: 0.45rem 0.7rem; + font-size: 0.7rem; + letter-spacing: 0.12em; + background: color-mix(in srgb, var(--surface-1) 92%, transparent); +} + +.topbar__icon-btn.p-button, .topbar__logout-btn.p-button{ + height: 2.45rem; + min-width: 2.45rem; +} + +.sidebar-brand{ + padding-bottom: 1rem; +} + +.sidebar-nav__item{ + min-height: 40px; + padding: 0.62rem 0.75rem; + border-radius: 10px; +} + +.sidebar-nav__item i{ + font-size: 0.92rem; +} + +.page-header{ + padding: 1.6rem 0 1rem; + margin-bottom: 1.2rem; +} + +.page-header__title{ + font-size: clamp(1.65rem, 3vw, 2.6rem); + letter-spacing: 0.05em; +} + +.page-header__subtitle, .section-card__subtitle{ + font-size: 0.92rem; + line-height: 1.65; +} + +.section-card.p-card, .stat-card.p-card{ + border-radius: 18px; + box-shadow: var(--shadow-md); +} + +.section-card .p-card-content, .stat-card .p-card-content{ + padding: 1rem 1.05rem; +} + +.section-card__header{ + padding-bottom: 0.8rem; + margin-bottom: 0.8rem; +} + +.section-card__title{ + font-size: 0.96rem; + letter-spacing: 0.06em; +} + +.stat-card__value{ + font-size: clamp(1.35rem, 2.2vw, 1.8rem); +} + +.stat-card__hint, .metric-tile span, .table-secondary, .form-field label, .topbar__user-meta small, .settings-toggle small{ + font-size: 0.78rem; +} + +body.dark-theme .dashboard-hero__title{ + font-size: clamp(1.8rem, 4vw, 3.1rem); + letter-spacing: 0.04em; +} + +.metric-tile strong, .settings-toggle strong{ + font-size: 0.88rem; +} + +.metric-tile, .settings-toggle{ + border-radius: 12px; +} + +.p-button{ + min-height: 2.55rem; + padding: 0.66rem 0.9rem; + font-size: 0.74rem; + font-weight: 600; + letter-spacing: 0.1em; + border-radius: 9px; +} + +.p-button:not(.p-disabled):hover{ + transform: none; + filter: brightness(1.04); +} + +.p-button.p-button-secondary, .p-button.p-button-help, .p-button.p-button-outlined, .p-button.p-button-text{ + background: color-mix(in srgb, var(--surface-1) 92%, transparent); +} + +.p-button.table-action-btn{ + width: 2.15rem; + min-width: 2.15rem; + height: 2.15rem; + border-radius: 8px; +} + +.p-inputtext, .p-dropdown, .p-inputtextarea, textarea.p-inputtextarea{ + border-radius: 10px; + padding: 0.72rem 0.82rem; + font-size: 0.92rem; + background: color-mix(in srgb, var(--surface-1) 88%, transparent); +} + +.p-inputswitch{ + width: 3.2rem; + height: 1.7rem; +} + +.p-inputswitch .p-inputswitch-slider::before{ + width: 1.1rem; + height: 1.1rem; + margin-top: -0.55rem; +} + +.p-tag{ + font-size: 0.66rem; +} + +.app-table{ + border-radius: 14px; +} + +.app-table .p-datatable-wrapper{ + border-radius: 14px; +} + +.app-table .p-datatable-thead > tr > th{ + font-size: 0.7rem; + letter-spacing: 0.1em; + padding: 0.8rem 0.9rem; +} + +.app-table .p-datatable-tbody > tr > td{ + padding: 0.82rem 0.9rem; + font-size: 0.9rem; +} + + + + + + + + + + + + + + + + + + + + + + + + + +.form-field{ + gap: 0.4rem; +} + +.form-field label{ + font-size: 0.72rem; + letter-spacing: 0.12em; +} + +@media (max-width: 991px) { + + + +} + + +/* v8 final polish */ +html{ + font-size: 14px; +} + +body{ + font-size: 0.92rem; +} + +.layout-content{ + padding: 0 1.5rem 1.5rem; +} + +.topbar{ + min-height: 68px; + padding: 0.85rem 1.5rem; + background: rgba(255, 255, 255, 0.12); +} + +body.dark-theme .topbar{ + background: rgba(23, 33, 43, 0.74); +} + +.topbar__headline{ + font-size: 1.2rem; + letter-spacing: 0.03em; +} + +.topbar__caption{ + font-size: 0.65rem; +} + +.topbar__user-meta strong{ + font-size: 0.86rem; +} + +.topbar__user-meta small{ + font-size: 0.68rem; +} + +.layout-sidebar{ + padding: 1rem 0.9rem; +} + +.sidebar-brand__logo{ + width: 2.2rem; + height: 2.2rem; + font-size: 0.8rem; +} + +.sidebar-brand__text h2{ + font-size: 0.95rem; +} + +.sidebar-brand__text p, .sidebar-section__label, .sidebar-nav__item span{ + font-size: 0.78rem; +} + +.sidebar-nav__item{ + min-height: 2.5rem; + border-radius: 10px; +} + +.p-button{ + min-height: 2.45rem; + padding: 0.55rem 0.9rem; + border-radius: 10px; + font-size: 0.82rem; + box-shadow: none; +} + +.p-button .p-button-label{ + font-weight: 500; +} + +.p-button.table-action-btn, .topbar__icon-btn.p-button{ + min-height: 2.2rem; + width: 2.2rem; + padding: 0; +} + +.topbar__logout-btn.p-button{ + min-height: 2.2rem; + padding: 0.45rem 0.75rem; +} + + + + + + + + + + + + + + + + + + + + + +.form-field label{ + font-size: 0.72rem; +} + +.p-inputtext, .p-dropdown, .p-multiselect, .p-inputnumber-input, .p-password-input{ + min-height: 2.5rem; + font-size: 0.84rem; +} + +.p-inputswitch{ + transform: scale(0.88); + transform-origin: left center; +} + +.page-header__title{ + font-size: 1.55rem; +} + +.section-card__title, .stat-card__value{ + font-size: 0.95rem; +} + +.p-datatable .p-datatable-thead > tr > th, .p-datatable .p-datatable-tbody > tr > td{ + font-size: 0.79rem; +} + +.status-dot{ + display: none; +} + +/* v9 UI fixes */ +:root{ + --primary-contrast: #ffffff; +} + +body.dark-theme{ + --primary-contrast: #101316; +} + +.p-button{ + color: var(--primary-contrast); +} + +.p-button.p-button-secondary, .p-button.p-button-help, .p-button.p-button-outlined, .p-button.p-button-text{ + color: var(--text-main); +} + +.metric-tile{ + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + gap: 0.45rem; + min-height: 6rem; +} + +.metric-tile span, .metric-tile strong{ + white-space: normal; + word-break: break-word; +} + +.repository-card__actions{ + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.repository-table .p-datatable-tbody > tr > td{ + vertical-align: top; +} + +.table-actions--labels{ + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.p-button.table-action-btn--wide{ + width: auto; + min-width: auto; + padding-inline: 0.8rem; +} + +.preview-dialog .p-dialog-header{ + align-items: center; +} + +.preview-dialog__actions{ + margin-bottom: 0.85rem; + justify-content: flex-end; +} + +.preview-dialog__content{ + max-height: 68vh; + margin: 0; +} + +.change-password-form{ + max-width: none; + width: 100%; +} + +.p-toast{ + width: min(26rem, calc(100vw - 2rem)); +} + +.p-toast .p-toast-message{ + border-radius: 16px; + border: 1px solid var(--border-color); + box-shadow: var(--shadow-md); + overflow: hidden; +} + +.p-toast .p-toast-message-content{ + gap: 0.8rem; +} + +.p-confirm-dialog .p-dialog-content{ + line-height: 1.65; +} + +@media (max-width: 720px) { + .change-password-form{ + grid-template-columns: 1fr; + } + + .table-actions--labels{ + flex-direction: column; + align-items: stretch; + } +} + + +/* --- 2026 UI refresh --- */ +.sidebar-brand__logo img{ + width: 100%; + height: 100%; + object-fit: contain; +} + +.sidebar-brand__logo{ + width: 3.25rem; + height: 3.25rem; + border-radius: 22px; + background: rgba(255, 255, 255, 0.92); + padding: 0.55rem; + display: grid; + place-items: center; + box-shadow: inset 0 0 0 1px rgba(17, 20, 23, 0.08); +} + +.topbar__user{ + padding: 0.35rem 0.65rem 0.35rem 0.35rem; + border: 1px solid var(--border-color); + border-radius: 999px; + background: var(--surface-1); +} + +.topbar__avatar{ + background: linear-gradient(135deg, var(--blue), var(--accent)); + color: #fff; + border: 2px solid rgba(255, 255, 255, 0.16); + box-shadow: 0 10px 24px rgba(75, 144, 217, 0.22); +} + +body.dark-theme .topbar__avatar{ + color: #f8fbff; + border-color: rgba(255, 255, 255, 0.18); +} + +.dashboard-grid--feature{ + align-items: stretch; + margin-top: 1.5rem; +} + +app-section-card.dashboard-operations-card{ + display: block; + margin-bottom: 0.5rem; +} + +.storage-panel{ + display: grid; + grid-template-columns: minmax(220px, 320px) minmax(0, 1fr); + gap: 1.25rem; + align-items: center; +} + +.storage-panel__visual, .storage-panel__stats{ + min-width: 0; +} + +.storage-ring{ + width: 180px; + height: 180px; + border-radius: 50%; + padding: 14px; + margin-bottom: 1rem; +} + +.storage-ring__inner{ + width: 100%; + height: 100%; + border-radius: 50%; + background: var(--surface-1); + display: grid; + place-items: center; + text-align: center; + border: 1px solid var(--border-color); +} + +.storage-ring__inner strong{ + display: block; + font-size: 1.8rem; + line-height: 1; +} + +.storage-ring__inner span{ + color: var(--text-soft); + font-size: 0.78rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.storage-bars{ + display: grid; + gap: 0.9rem; +} + +.storage-bars__meta{ + display: flex; + justify-content: space-between; + gap: 0.8rem; + margin-bottom: 0.35rem; +} + +.storage-bars__track{ + height: 12px; + border-radius: 999px; + background: rgba(128, 145, 164, 0.14); + overflow: hidden; +} + +.storage-bars__track span{ + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, var(--accent), var(--blue)); +} + +.metric-grid-2--dense{ + gap: 0.95rem; +} + +.metric-tile--feature{ + min-height: 106px; + justify-content: space-between; +} + + +.repository-toolbar, .compare-strip{ + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: 0.9rem; + margin-bottom: 1rem; +} + +.repository-toolbar__search{ + grid-column: span 4; +} + +.repository-toolbar .form-field, .compare-strip__slot{ + grid-column: span 2; +} + +.repository-toolbar__actions, .compare-strip__actions{ + grid-column: span 2; + align-self: end; +} + +.compare-strip{ + padding: 1rem; + border: 1px solid var(--border-color); + border-radius: 22px; + background: linear-gradient(180deg, rgba(75, 144, 217, 0.06), rgba(75, 144, 217, 0.02)); +} + +.compare-strip__swap{ + align-self: end; +} + +.table-actions--stack{ + flex-direction: column; + align-items: stretch; +} + +.github-diff{ + border: 1px solid var(--border-color); + border-radius: 18px; + overflow: hidden; + max-height: 70vh; + overflow-y: auto; +} + +.github-diff__row{ + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); +} + +.github-diff__cell{ + display: grid; + grid-template-columns: 56px minmax(0, 1fr); + min-width: 0; +} + +.github-diff__number{ + padding: 0.65rem 0.45rem; + border-right: 1px solid var(--border-color); + text-align: right; + color: var(--text-faint); + background: rgba(128, 145, 164, 0.08); + font-family: var(--font-title); + font-size: 0.78rem; +} + +.github-diff__cell pre{ + margin: 0; + padding: 0.65rem 0.85rem; + white-space: pre-wrap; + word-break: break-word; + font-family: var(--font-title); + line-height: 1.5; +} + +.github-diff__row[data-type="added"] .github-diff__cell--right, .github-diff__row[data-type="modified"] .github-diff__cell--right{ + background: rgba(79, 181, 147, 0.12); +} + +.github-diff__row[data-type="removed"] .github-diff__cell--left, .github-diff__row[data-type="modified"] .github-diff__cell--left{ + background: rgba(227, 122, 122, 0.12); +} + +.diff-layout__summary{ + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto auto; + gap: 1rem; + align-items: center; + margin-bottom: 1rem; +} + +.diff-layout__summary-arrow{ + color: var(--text-faint); +} + +.diff-stats{ + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.diff-stats__pill{ + padding: 0.45rem 0.7rem; + border-radius: 999px; + font-family: var(--font-title); + font-size: 0.78rem; +} + +.diff-stats__pill--added{ + background: rgba(79, 181, 147, 0.14); + color: var(--success); +} + +.diff-stats__pill--removed{ + background: rgba(227, 122, 122, 0.14); + color: var(--danger); +} + +.diff-stats__pill--modified{ + background: rgba(240, 180, 91, 0.14); + color: var(--warning); +} + +.settings-grid--enhanced{ + align-items: start; +} + +.settings-status-grid{ + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 1rem; + margin-bottom: 1.35rem; +} + +.settings-status-card{ + padding: 1rem; + border: 1px solid var(--border-color); + border-radius: 22px; + background: var(--surface-0); + box-shadow: var(--shadow-md); +} + +.settings-status-card__header{ + display: flex; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0.75rem; +} + +.settings-status-card__description{ + font-family: var(--font-title); + margin-bottom: 0.25rem; +} + +.settings-scheduler-stack{ + display: grid; + gap: 1rem; +} + +.scheduler-card{ + padding: 1rem; + border-radius: 22px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, rgba(75, 144, 217, 0.05), rgba(75, 144, 217, 0.01)); +} + +.scheduler-card__header{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: center; + margin-bottom: 1rem; +} + +.scheduler-card__header small{ + display: block; + margin-top: 0.3rem; +} + +.scheduler-card__grid{ + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.9rem; +} + +.time-picker{ + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + align-items: center; + gap: 0.45rem; +} + +.settings-toggle--inline{ + gap: 0.7rem; +} + +.settings-actions--sticky{ + position: sticky; + bottom: 1rem; + justify-content: flex-end; + padding: 0.8rem 1rem; + border-radius: 18px; + background: rgba(23, 33, 43, 0.08); + backdrop-filter: blur(10px); +} + +body.dark-theme .settings-actions--sticky{ + background: rgba(7, 12, 18, 0.45); +} + +.change-password-shell{ + display: grid; + grid-template-columns: minmax(280px, 360px) minmax(0, 1fr); + gap: 1.25rem; +} + +.password-insights{ + display: grid; + gap: 1rem; +} + +.password-strength{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: center; +} + +.password-strength__track{ + height: 12px; + border-radius: 999px; + background: rgba(128, 145, 164, 0.14); + overflow: hidden; +} + +.password-strength__track span{ + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, var(--danger), var(--warning), var(--success)); +} + +.password-checklist{ + display: grid; + gap: 0.75rem; +} + +.password-checklist__item{ + display: flex; + align-items: center; + gap: 0.7rem; + color: var(--text-soft); +} + +.password-checklist__item.is-ready{ + color: var(--text-main); +} + +.change-password-form--expanded{ + align-items: start; +} + +@media (max-width: 1280px) { + .repository-toolbar__search, .repository-toolbar .form-field, .repository-toolbar__actions, .compare-strip__slot, .compare-strip__actions{ + grid-column: span 6; + } + + .settings-status-grid{ + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .scheduler-card__grid, .storage-panel, .change-password-shell{ + grid-template-columns: 1fr; + } +} + +@media (max-width: 860px) { + .diff-layout__summary{ + grid-template-columns: 1fr; + } + + .github-diff__row{ + grid-template-columns: 1fr; + } + + .repository-toolbar__search, .repository-toolbar .form-field, .repository-toolbar__actions, .compare-strip__slot, .compare-strip__actions, .settings-status-grid{ + grid-column: span 12; + } + + .settings-status-grid{ + grid-template-columns: 1fr; + } +} + + +/* patch set: settings, dashboard, repository, logs */ +.inline-summary{ + display: flex; + align-items: center; + gap: 1rem; + flex-wrap: wrap; + margin-bottom: 1rem; + padding: 0.95rem 1.05rem; + border: 1px solid var(--border-color); + border-radius: 18px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.18)); +} + +body.dark-theme .inline-summary{ + background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01)); +} + +.inline-summary--soft{ + box-shadow: var(--shadow-md); +} + +.inline-summary--tight{ + margin-bottom: 1.2rem; +} + +.inline-summary__item{ + display: flex; + flex-direction: column; + gap: 0.2rem; +} + +.inline-summary__item strong{ + font-family: var(--font-title); + font-size: 1rem; + letter-spacing: 0.04em; +} + +.inline-summary__item span{ + color: var(--text-soft); + font-size: 0.76rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.inline-summary__divider{ + width: 1px; + align-self: stretch; + background: var(--border-color); +} + +.settings-automation-intro{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: center; + margin-bottom: 1rem; + padding: 0.95rem 1rem; + border: 1px dashed var(--border-strong); + border-radius: 18px; + background: rgba(75, 144, 217, 0.04); +} + +.settings-automation-intro strong, .repository-compare__header strong{ + font-family: var(--font-title); + font-size: 0.95rem; + letter-spacing: 0.05em; +} + +.settings-automation-intro p, .repository-compare__header p, .scheduler-card__hint{ + margin: 0.25rem 0 0; + color: var(--text-soft); + line-height: 1.55; +} + +.scheduler-card__hint{ + margin-bottom: 1rem; + font-size: 0.84rem; +} + +.choice-toggle{ + display: inline-grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.35rem; + padding: 0.3rem; + border: 1px solid var(--border-color); + border-radius: 999px; + background: color-mix(in srgb, var(--surface-1) 92%, transparent); +} + +.choice-toggle__btn{ + min-width: 92px; + border: 0; + border-radius: 999px; + padding: 0.52rem 0.8rem; + background: transparent; + color: var(--text-soft); + font-family: var(--font-title); + font-size: 0.72rem; + letter-spacing: 0.12em; + text-transform: uppercase; + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; +} + +.choice-toggle__btn.is-active{ + background: var(--primary); + color: #fff; + box-shadow: var(--shadow-md); +} + +.settings-toggle{ + align-items: flex-start; +} + +.operations-center{ + display: grid; + grid-template-columns: minmax(240px, 320px) minmax(0, 1fr); + gap: 1rem; + align-items: stretch; +} + +.operations-center__actions{ + display: grid; + gap: 0.85rem; + padding: 1rem; + border-radius: 20px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, rgba(75, 144, 217, 0.06), rgba(75, 144, 217, 0.015)); +} + +.operations-center__actions .p-button{ + justify-content: center; +} + +.operations-center__stats{ + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.85rem; +} + +.operations-center__stats .metric-tile small{ + margin-top: 0.35rem; + color: var(--text-soft); + line-height: 1.45; +} + +.repository-compare{ + margin-top: 1rem; + padding: 1rem; + border: 1px solid var(--border-color); + border-radius: 22px; + background: linear-gradient(180deg, rgba(75, 144, 217, 0.06), rgba(75, 144, 217, 0.02)); +} + +.repository-compare__header{ + display: flex; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; + align-items: flex-start; +} + +.repository-compare__status{ + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 0.5rem; +} + +.repository-compare__grid{ + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto; + gap: 0.9rem; + align-items: end; +} + +.repository-compare__slot{ + min-width: 0; +} + +.repository-compare__actions{ + display: grid; + gap: 0.65rem; +} + +.repository-table .p-datatable-tbody > tr[data-compare-role="left"] > td{ + background: rgba(240, 180, 91, 0.07); +} + +.repository-table .p-datatable-tbody > tr[data-compare-role="right"] > td{ + background: rgba(79, 181, 147, 0.08); +} + +.repository-table .p-datatable-tbody > tr[data-compare-role="left"]:hover > td, .repository-table .p-datatable-tbody > tr[data-compare-role="right"]:hover > td{ + filter: brightness(1.01); +} + +.change-password-form .p-inputtext{ + width: 100%; +} + +@media (max-width: 1280px) { + .operations-center, .operations-center__stats, .repository-compare__grid{ + grid-template-columns: 1fr; + } + + .repository-compare__status{ + justify-content: flex-start; + } +} + +@media (max-width: 860px) { + .settings-automation-intro, .repository-compare__header{ + flex-direction: column; + } + + .choice-toggle{ + width: 100%; + } + + .choice-toggle__btn{ + min-width: 0; + } + + .inline-summary__divider{ + display: none; + } +} + + + + +.layout-footer a{ + color: var(--accent); +} + +.layout-footer__status{ + display: inline-flex; + align-items: center; + gap: 0.45rem; +} + +.layout-footer__status::before{ + content: ""; + width: 0.6rem; + height: 0.6rem; + border-radius: 999px; + background: var(--text-faint); + box-shadow: 0 0 0 4px rgba(128, 145, 164, 0.12); +} + +.layout-footer__status--online::before{ + background: var(--success); + box-shadow: 0 0 0 4px rgba(79, 181, 147, 0.16); +} + +.layout-footer__status--offline::before{ + background: var(--danger); + box-shadow: 0 0 0 4px rgba(195, 70, 70, 0.14); +} + +.layout-footer__status--checking::before{ + background: var(--warning); + box-shadow: 0 0 0 4px rgba(240, 180, 91, 0.14); +} + + +.api-connection-banner{ + position: fixed; + right: 1.5rem; + bottom: 1.5rem; + z-index: 120; + display: flex; + align-items: center; + gap: 0.9rem; + width: min(28rem, calc(100vw - 2rem)); + padding: 0.95rem 1rem; + border-radius: 18px; + border: 1px solid rgba(195, 70, 70, 0.22); + background: rgba(255, 248, 248, 0.96); + box-shadow: var(--shadow-lg); +} + +body.dark-theme .api-connection-banner{ + background: rgba(29, 39, 51, 0.96); +} + +.api-connection-banner__content{ + display: grid; + gap: 0.25rem; +} + +.api-connection-banner__content strong{ + font-size: 0.84rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--danger); +} + +.api-connection-banner__content span{ + color: var(--text-soft); + font-size: 0.9rem; + line-height: 1.5; +} + +.api-connection-banner__action{ + flex-shrink: 0; + border: 1px solid rgba(195, 70, 70, 0.2); + border-radius: 999px; + background: transparent; + color: var(--danger); + padding: 0.65rem 0.95rem; + font: inherit; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + cursor: pointer; +} + +.api-connection-banner__action:hover{ + background: rgba(195, 70, 70, 0.08); +} + + +/* --- router detail adjustments --- */ +.router-detail-grid{ + align-items: start; +} + +.router-detail-grid--stack{ + grid-template-columns: 1fr; +} + +.router-status-panel{ + display: grid; + gap: 0.9rem; +} + +.router-status-error{ + display: grid; + gap: 0.35rem; + padding: 0.85rem 1rem; + border-radius: 16px; + border: 1px solid rgba(217, 75, 91, 0.24); + background: rgba(217, 75, 91, 0.08); +} + +.router-status-note{ + color: var(--text-soft); +} + +.table-actions--tight{ + flex-wrap: nowrap; + gap: 0.4rem; + overflow-x: auto; + padding-bottom: 0.15rem; +} + +.p-button.table-action-btn--compact{ + min-height: 2rem; + padding-inline: 0.6rem; + font-size: 0.78rem; +} + +/* --- settings layout --- */ + +.settings-page-shell{ + display: grid; + gap: 1rem; +} + +.settings-page-columns{ + display: grid; + grid-template-columns: minmax(0, 1.65fr) minmax(320px, 0.95fr); + gap: 1rem; + align-items: start; +} + +.settings-page-main, .settings-page-side{ + display: grid; + gap: 1rem; +} + +.settings-collapse{ + border: 1px solid var(--border-color); + border-radius: 24px; + background: linear-gradient(180deg, var(--surface-1) 0%, var(--surface-0) 100%); + box-shadow: var(--shadow-md); + overflow: hidden; +} + +.settings-collapse > summary{ + position: relative; + list-style: none; + cursor: pointer; + padding: 1rem 3rem 1rem 1.15rem; + display: grid; + gap: 0.2rem; +} + +.settings-collapse > summary::-webkit-details-marker{ + display: none; +} + +.settings-collapse > summary span{ + font-family: var(--font-title); + font-size: 1rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.settings-collapse > summary small{ + color: var(--text-soft); +} + +.settings-collapse > summary::after{ + content: ""; + font-family: "primeicons"; + position: absolute; + right: 1.1rem; + top: 50%; + transform: translateY(-50%) rotate(0deg); + color: var(--text-soft); + transition: transform 0.2s ease; +} + +.settings-collapse[open] > summary::after{ + transform: translateY(-50%) rotate(180deg); +} + +.settings-collapse__body{ + padding: 0.85rem 1.15rem 1.15rem; + border-top: 1px solid var(--border-color); +} + +.settings-collapse__body > :first-child, .section-card .p-card-content > :first-child{ + margin-top: 0; +} + +.settings-collapse--sticky{ + position: sticky; + top: 1rem; +} + +.settings-scheduler-stack{ + margin-top: 1rem; +} + +.scheduler-card--subtle{ + background: linear-gradient(180deg, rgba(79, 181, 147, 0.08), rgba(79, 181, 147, 0.02)); +} + +.scheduler-card__grid--compact{ + grid-template-columns: minmax(0, 220px); +} + +.settings-ssh-panel{ + display: grid; + gap: 1rem; +} + +.settings-ssh-panel__header{ + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 1rem; +} + +.settings-ssh-panel__header p, .settings-ssh-lock p, .settings-ssh-note{ + color: var(--text-soft); +} + +.settings-ssh-lock{ + display: grid; + gap: 0.85rem; + padding: 1rem; + border-radius: 18px; + border: 1px dashed var(--border-color); + background: rgba(75, 144, 217, 0.05); +} + +.settings-ssh-actions{ + justify-content: flex-end; +} + + + +app-page-header, app-section-card, app-stat-card, app-sidebar, app-topbar{ + display: block; +} + +app-page-header{ + margin-bottom: 1rem; +} + +.section-card__header + *, .page-header__actions + *{ + margin-top: 0.25rem; +} + +.p-tag, .p-tag .p-tag-value{ + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +@media (max-width: 1120px) { + .settings-page-columns{ + grid-template-columns: 1fr; + } + + .settings-collapse--sticky{ + position: static; + } +} + +@media (max-width: 860px) { + .table-actions--tight{ + flex-wrap: wrap; + overflow-x: visible; + } + + .settings-collapse__body{ + padding-inline: 1rem; + } +} + +.router-detail-grid--inspection{ + align-items: start; +} + +.router-detail-inspection-stack{ + display: grid; + gap: 1rem; +} + +.router-detail-grid--stack{ + margin-top: 1rem; +} + +.router-modal-summary{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: center; + padding: 1rem; + border: 1px dashed var(--border-color); + border-radius: 18px; + background: linear-gradient(180deg, rgba(75, 144, 217, 0.05), rgba(75, 144, 217, 0.015)); +} + +.router-modal-summary strong, .diff-pick-card__meta strong, .activity-feed__header strong{ + display: block; + margin: 0; +} + +.router-modal-summary small, .diff-pick-card__meta small{ + display: block; + margin-top: 0.35rem; + color: var(--text-soft); +} + +.repository-table, .dashboard-grid > app-section-card, .router-detail-inspection-stack > app-section-card{ + width: 100%; +} + +.activity-feed{ + display: grid; + gap: 0.55rem; +} + +.activity-feed__item{ + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + gap: 0.75rem; + align-items: start; + padding: 0.75rem 0; + border-bottom: 1px solid var(--border-color); +} + +.activity-feed__item:last-child{ + border-bottom: 0; +} + +.activity-feed__dot{ + width: 28px; + height: 28px; + border-radius: 999px; + display: grid; + place-items: center; + color: var(--text-soft); + background: rgba(75, 144, 217, 0.08); + border: 1px solid rgba(75, 144, 217, 0.12); + font-size: 0.8rem; +} + +.activity-feed__item[data-tone="danger"] .activity-feed__dot{ + color: var(--danger); + background: rgba(227, 122, 122, 0.08); + border-color: rgba(227, 122, 122, 0.18); +} + +.activity-feed__item[data-tone="warning"] .activity-feed__dot{ + color: var(--warning); + background: rgba(240, 180, 91, 0.08); + border-color: rgba(240, 180, 91, 0.18); +} + +.activity-feed__item[data-tone="success"] .activity-feed__dot{ + color: var(--success); + background: rgba(79, 181, 147, 0.08); + border-color: rgba(79, 181, 147, 0.18); +} + +.activity-feed__body{ + min-width: 0; +} + +.activity-feed__header{ + display: flex; + justify-content: space-between; + gap: 0.75rem; + align-items: center; + margin-bottom: 0.2rem; +} + +.activity-feed__header small{ + color: var(--text-faint); + white-space: nowrap; +} + +.activity-feed__message{ + color: var(--text-soft); + line-height: 1.45; + font-size: 0.88rem; +} + +.activity-feed__footer{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: center; + padding-top: 0.4rem; +} + +.diff-workspace{ + display: grid; + gap: 1rem; +} + +.diff-workspace__toolbar{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: end; + flex-wrap: wrap; +} + +.diff-workspace__router{ + min-width: min(320px, 100%); +} + +.diff-workspace__actions{ + display: flex; + gap: 0.7rem; + flex-wrap: wrap; +} + +.diff-workspace__pair{ + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + gap: 1rem; + align-items: center; +} + +.diff-workspace__swap.p-button{ + align-self: center; +} + +.diff-pick-card{ + display: grid; + gap: 0.9rem; + padding: 1rem; + border-radius: 22px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, var(--surface-1), rgba(255,255,255,0.02)); +} + +.diff-pick-card.is-selected{ + border-color: rgba(75, 144, 217, 0.3); + box-shadow: var(--shadow-md); +} + +.diff-pick-card__header{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: center; +} + +.diff-pick-card__header strong{ + font-family: var(--font-title); + letter-spacing: 0.06em; +} + +.diff-pick-card__meta{ + padding-top: 0.3rem; + border-top: 1px solid var(--border-color); +} + +@media (max-width: 980px) { + .diff-workspace__pair, .router-detail-grid--inspection{ + grid-template-columns: 1fr; + } + + .diff-workspace__swap.p-button{ + justify-self: stretch; + } +} + +@media (max-width: 720px) { + .activity-feed__header, .router-modal-summary{ + flex-direction: column; + align-items: flex-start; + } +} + + +/* --- patch set: compact repository ux and spacing refinements --- */ +.repository-table-section, .diff-configs-table-section{ + display: block; + margin-top: 1rem; +} + +.repository-table.app-table{ + border-radius: 16px; +} + +.repository-table.app-table .p-datatable-thead > tr > th{ + padding: 0.72rem 0.8rem; + font-size: 0.68rem; + letter-spacing: 0.1em; +} + +.repository-table.app-table .p-datatable-tbody > tr > td{ + padding: 0.68rem 0.8rem; +} + +.repository-table .table-primary{ + font-size: 0.88rem; + line-height: 1.35; +} + +.repository-table .table-secondary{ + margin-top: 0.18rem; + font-size: 0.74rem; + line-height: 1.35; +} + +.repository-table .p-tag{ + font-size: 0.68rem; + padding: 0.22rem 0.48rem; +} + +.repository-table .p-checkbox{ + transform: scale(0.92); + transform-origin: center; +} + +.repository-table .p-datatable-tbody > tr > td:first-child, .repository-table .p-datatable-thead > tr > th:first-child{ + width: 2.6rem; + padding-inline: 0.55rem; +} + +.repository-table .table-actions--stack{ + gap: 0.38rem; +} + +.repository-table .p-button.table-action-btn, .repository-table .p-button.table-action-btn--wide, .repository-table .p-button.table-action-btn--compact{ + min-height: 1.9rem; + padding: 0.45rem 0.62rem; + font-size: 0.7rem; + letter-spacing: 0.09em; +} + +.repository-table .p-button .p-button-label{ + white-space: nowrap; +} + +.repository-table .p-button .p-button-icon{ + font-size: 0.78rem; +} + +.repository-table .p-paginator{ + padding: 0.4rem 0.65rem; +} + +.repository-table .p-paginator .p-paginator-pages .p-paginator-page, .repository-table .p-paginator .p-paginator-next, .repository-table .p-paginator .p-paginator-prev, .repository-table .p-paginator .p-dropdown{ + min-width: 2rem; + height: 2rem; +} + +.repository-compare{ + margin-top: 1.15rem; +} + +.repository-compare__grid{ + margin-top: 0.2rem; +} + +.diff-workspace{ + gap: 1.15rem; +} + +.diff-workspace__pair{ + margin-top: 0.15rem; +} + +.preview-dialog .p-dialog-header{ + padding-bottom: 0.9rem; +} + +.preview-dialog .p-dialog-content{ + padding-top: 1rem; +} + +@media (max-width: 980px) { + .repository-table-section, .diff-configs-table-section{ + margin-top: 0.85rem; + } + + .repository-table.app-table .p-datatable-thead > tr > th, .repository-table.app-table .p-datatable-tbody > tr > td{ + padding-inline: 0.72rem; + } +} + +/* --- 2026 patch: dashboard storage, confirm dialog, interface prefs, switches --- */ +.storage-panel--enhanced{ + gap: 1.15rem; +} + +.storage-browser__switch.is-active{ + color: var(--text-main); + border-color: color-mix(in srgb, var(--accent) 36%, var(--border-color)); + box-shadow: 0 10px 24px color-mix(in srgb, var(--accent) 12%, transparent); +} + +.storage-browser__switch.is-active{ + background: color-mix(in srgb, var(--surface-1) 96%, transparent); +} + +.storage-browser__canvas, .storage-chart{ + display: grid; + gap: 0.85rem; +} + +.storage-chart-row{ + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(140px, 1.25fr) auto; + align-items: center; + gap: 0.85rem; +} + +.storage-chart-row__meta{ + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; +} + +.storage-chart-row__meta span, .storage-chart-row small, .storage-column small{ + color: var(--text-soft); + font-size: 0.72rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.storage-chart-row__meta strong, .storage-column strong{ + font-size: 0.92rem; +} + +.storage-bars__track span, .storage-chart-row__track span, .storage-stackbar__segment, .storage-column__track span{ + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, var(--accent), var(--blue)); +} + +.storage-bars__track span[data-tone="success"], .storage-chart-row__track span[data-tone="success"], .storage-stackbar__segment[data-tone="success"]{ + background: linear-gradient(90deg, var(--accent-2), var(--success)); +} + +.storage-bars__track span[data-tone="warning"], .storage-chart-row__track span[data-tone="warning"], .storage-stackbar__segment[data-tone="warning"]{ + background: linear-gradient(90deg, var(--warning), color-mix(in srgb, var(--warning) 44%, var(--danger))); +} + +.storage-bars__track span[data-tone="info"], .storage-chart-row__track span[data-tone="info"], .storage-stackbar__segment[data-tone="info"]{ + background: linear-gradient(90deg, color-mix(in srgb, var(--blue) 78%, #fff 0%), var(--blue)); +} + +.storage-stackbar{ + display: flex; + min-height: 16px; + border-radius: 999px; + overflow: hidden; + background: rgba(128, 145, 164, 0.14); +} + +.storage-columns{ + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 0.75rem; + align-items: end; +} + +.storage-column{ + display: grid; + gap: 0.55rem; + justify-items: center; +} + +.storage-column__track{ + height: 150px; + width: 100%; + padding: 0 0.35rem; + display: flex; + align-items: flex-end; + border-radius: 18px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, color-mix(in srgb, var(--surface-1) 92%, transparent), transparent); +} + +.storage-column__track span{ + width: 100%; + min-height: 6px; + border-radius: 14px 14px 8px 8px; + background: linear-gradient(180deg, var(--accent), var(--blue)); +} + +.storage-chart-empty{ + padding: 1rem; + border-radius: 18px; + border: 1px dashed var(--border-strong); + color: var(--text-soft); + text-align: center; +} + +.storage-snapshot-grid{ + gap: 0.8rem; +} + +.storage-snapshot-card{ + min-height: 96px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, color-mix(in srgb, var(--surface-1) 94%, transparent), color-mix(in srgb, var(--surface-0) 94%, transparent)); +} + +.layout-footer__author{ + display: inline-flex; + align-items: center; + gap: 0.55rem; + padding: 0.45rem 0.85rem; + border-radius: 999px; + border: 1px solid var(--border-color); + background: color-mix(in srgb, var(--surface-1) 90%, transparent); +} + +.layout-footer__author-label{ + color: var(--text-soft); +} + +.layout-footer__author strong{ + font-size: 0.82rem; + text-transform: none; + letter-spacing: 0.04em; +} + +.layout-footer__author a{ + font-size: 0.78rem; + font-weight: 700; +} + +@media (max-width: 1180px) { + .storage-panel{ + grid-template-columns: 1fr; + } +} + +@media (max-width: 860px) { + .storage-hero{ + grid-template-columns: 1fr; + } + + .storage-ring{ + margin-inline: auto; + } + + .storage-browser__switch{ + flex: 1 1 calc(50% - 0.55rem); + } +} + +@media (max-width: 720px) { + .storage-chart-row{ + grid-template-columns: 1fr; + } + + .storage-chart-row__meta{ + flex-direction: column; + gap: 0.2rem; + } + + .storage-column__track{ + height: 120px; + } + + .layout-footer__author{ + width: 100%; + justify-content: center; + } +} + +.settings-interface-intro, .settings-automation-intro{ + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; + padding: 0.95rem 1rem; + border: 1px dashed var(--border-strong); + border-radius: 18px; + background: rgba(75, 144, 217, 0.04); +} + +.settings-interface-intro strong, .settings-automation-intro strong, .repository-compare__header strong{ + font-family: var(--font-title); + font-size: 0.95rem; + letter-spacing: 0.05em; +} + +.settings-interface-intro p, .settings-automation-intro p, .repository-compare__header p, .scheduler-card__hint{ + margin: 0.25rem 0 0; + color: var(--text-soft); + line-height: 1.55; +} + +.choice-toggle{ + overflow: hidden; +} + +.choice-toggle__btn{ + font-weight: 700; +} + +body.dark-theme .choice-toggle{ + background: rgba(8, 14, 22, 0.52); + border-color: rgba(146, 170, 194, 0.24); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03); +} + +body.dark-theme .choice-toggle__btn{ + color: #b8c8d8; +} + +body.dark-theme .choice-toggle__btn:hover{ + background: rgba(255, 255, 255, 0.06); + color: #edf3f8; +} + +body.dark-theme .choice-toggle__btn.is-active{ + background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 82%, #fff 0%), color-mix(in srgb, var(--blue) 85%, #fff 0%)); + color: #0f1720; +} + +.p-confirm-dialog{ + border-radius: 24px; + overflow: hidden; + border: 1px solid var(--border-color); + box-shadow: var(--shadow-lg); +} + +.p-confirm-dialog .p-dialog-header{ + padding: 1rem 1.15rem 0.75rem; + background: linear-gradient(180deg, color-mix(in srgb, var(--surface-1) 98%, transparent), color-mix(in srgb, var(--surface-0) 98%, transparent)); + border-bottom: 1px solid var(--border-color); +} + +.p-confirm-dialog .p-dialog-title{ + font-family: var(--font-title); + letter-spacing: 0.08em; +} + +.p-confirm-dialog .p-dialog-content{ + padding: 1rem 1.15rem 0.5rem; + line-height: 1.65; + background: color-mix(in srgb, var(--surface-0) 98%, transparent); +} + +.p-confirm-dialog .p-confirm-dialog-icon{ + width: 2.6rem; + height: 2.6rem; + display: inline-grid; + place-items: center; + border-radius: 16px; + background: color-mix(in srgb, var(--warning) 14%, transparent); + color: var(--warning); + margin-right: 0.85rem; +} + +.p-confirm-dialog .p-dialog-footer{ + display: flex; + justify-content: flex-end; + gap: 0.65rem; + padding: 0.9rem 1.15rem 1rem; + background: color-mix(in srgb, var(--surface-1) 98%, transparent); + border-top: 1px solid var(--border-color); +} + +body.dark-theme .p-confirm-dialog .p-confirm-dialog-icon{ + background: rgba(240, 180, 91, 0.14); +} + +@media (max-width: 980px) { + .storage-hero{ + grid-template-columns: 1fr; + justify-items: center; + text-align: center; + } + + .storage-hero__summary{ + width: 100%; + } +} + + +/* --- 2026 patch: dashboard width, auth layout, settings actions --- */ +.dashboard-stack{ + margin-top: 1.5rem; +} + +.dashboard-stack > app-section-card{ + display: block; +} + +.settings-actions, .settings-actions--sticky{ + justify-content: center; +} + + + + + + + + + +@media (max-width: 991px) { + +} + +/* patch set: dashboard capacity and switchos beta */ +.storage-browser__header{ + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; +} + +.storage-browser__copy, .storage-browser__switcher, .storage-chart-row__meta{ + min-width: 0; +} + +.storage-browser__switcher{ + justify-content: flex-end; +} + +.storage-chart-row__meta{ + flex-wrap: wrap; +} + +.storage-chart-row__meta strong{ + white-space: nowrap; + flex-shrink: 0; +} + +.beta-banner{ + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + padding: 1rem 1.05rem; + border-radius: 16px; + border: 1px solid color-mix(in srgb, var(--warning) 36%, var(--border-color)); + background: linear-gradient(180deg, color-mix(in srgb, var(--warning) 10%, transparent), color-mix(in srgb, var(--surface-1) 94%, transparent)); +} + +.beta-banner p{ + margin: 0.45rem 0 0; + color: var(--text-soft); + line-height: 1.6; +} + +.swos-beta-grid{ + align-items: start; +} + +.swos-beta-actions{ + grid-column: 1 / -1; + justify-content: flex-start; + flex-wrap: wrap; +} + +.swos-beta-result{ + display: grid; + gap: 0.85rem; +} + +.swos-beta-result__item{ + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.9rem 1rem; + border-radius: 14px; + border: 1px solid var(--border-color); + background: color-mix(in srgb, var(--surface-1) 90%, transparent); +} + +.swos-beta-result__item span{ + color: var(--text-soft); + font-size: 0.78rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.swos-beta-result__item strong{ + text-align: right; +} + +.swos-beta-note, .beta-error{ + padding: 0.9rem 1rem; + border-radius: 14px; + line-height: 1.6; +} + +.swos-beta-note{ + border: 1px solid var(--border-color); + color: var(--text-soft); + background: color-mix(in srgb, var(--surface-1) 88%, transparent); +} + +.beta-error{ + border: 1px solid color-mix(in srgb, var(--danger) 38%, var(--border-color)); + color: color-mix(in srgb, var(--danger) 82%, var(--text-main)); + background: color-mix(in srgb, var(--danger) 8%, transparent); +} + +@media (max-width: 1024px) { + .storage-browser__header{ + grid-template-columns: 1fr; + } + + .storage-browser__switcher{ + width: 100%; + justify-content: flex-start; + } +} + +@media (max-width: 720px) { + .beta-banner, .swos-beta-result__item{ + flex-direction: column; + align-items: flex-start; + } + + .swos-beta-result__item strong{ + text-align: left; + } +} + +.storage-browser__header{ + grid-template-columns: minmax(0, 1fr); + gap: 1rem; +} + +.storage-browser__copy{ + display: grid; + gap: 0.35rem; +} + +.storage-browser__copy strong{ + display: block; +} + +.storage-browser__switcher{ + width: 100%; + justify-content: flex-start; +} + +@media (max-width: 991px) { + +} + +.router-dialog .p-dialog-header{ + padding: 1.15rem 1.2rem 1rem; + align-items: flex-start; + background: + linear-gradient(135deg, rgba(75, 144, 217, 0.16), rgba(79, 181, 147, 0.1)), + linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), + var(--surface-1); + border-bottom: 1px solid rgba(75, 144, 217, 0.18); +} + +.router-dialog .p-dialog-header-icons{ + align-self: flex-start; + margin-top: 0.2rem; +} + +.router-dialog .p-dialog-content{ + padding: 0 1.2rem 1.2rem; + background: linear-gradient(180deg, rgba(75, 144, 217, 0.06) 0%, rgba(75, 144, 217, 0) 180px), var(--surface-1); +} + +.router-dialog-header{ + display: flex; + align-items: center; + gap: 0.9rem; + width: calc(100% - 0.5rem); +} + +.router-dialog-header__icon{ + width: 3rem; + height: 3rem; + border-radius: 18px; + display: grid; + place-items: center; + flex-shrink: 0; + background: linear-gradient(135deg, rgba(75, 144, 217, 0.24), rgba(79, 181, 147, 0.14)); + border: 1px solid rgba(75, 144, 217, 0.2); + color: var(--primary); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.router-dialog-header__icon .pi{ + font-size: 1.05rem; +} + +.router-dialog-header__text{ + min-width: 0; +} + +.router-dialog-header__eyebrow{ + font-family: var(--font-title); + font-size: 0.72rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--text-soft); +} + +.router-dialog-header__title{ + margin-top: 0.2rem; + font-family: var(--font-title); + font-size: 1.18rem; + line-height: 1.25; +} + +.router-dialog-header__text small{ + display: block; + margin-top: 0.3rem; + max-width: 42rem; + line-height: 1.55; +} + +.router-dialog-form{ + display: grid; + gap: 1rem; +} + +.router-dialog-panel{ + padding: 1rem; + border-radius: 22px; + border: 1px solid var(--border-color); + background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), var(--surface-0); + box-shadow: var(--shadow-md); +} + +.router-dialog-panel:first-child{ + margin-top: 1rem; +} + +.router-dialog-panel__header{ + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 1rem; + margin-bottom: 0.95rem; +} + +.router-dialog-panel__header strong{ + display: block; + font-family: var(--font-title); + font-size: 0.88rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.router-dialog-panel__header p{ + margin: 0.35rem 0 0; + color: var(--text-soft); + line-height: 1.55; +} + +.router-dialog-pill{ + display: inline-flex; + align-items: center; + gap: 0.45rem; + padding: 0.55rem 0.85rem; + border-radius: 999px; + border: 1px solid rgba(75, 144, 217, 0.18); + background: rgba(75, 144, 217, 0.08); + color: var(--text-main); + font-family: var(--font-title); + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; + white-space: nowrap; +} + +.router-dialog-grid{ + gap: 0.95rem 1rem; +} + +.router-dialog-note{ + margin-top: 0.9rem; + padding: 0.85rem 0.95rem; + border-radius: 16px; + border: 1px solid rgba(75, 144, 217, 0.18); + background: rgba(75, 144, 217, 0.08); + color: var(--text-soft); + display: flex; + align-items: flex-start; + gap: 0.65rem; + line-height: 1.55; +} + +.router-dialog-note .pi{ + margin-top: 0.1rem; + color: var(--accent); +} + +.router-dialog .p-inputtext, +.router-dialog .p-dropdown, +.router-dialog .p-inputtextarea{ + background: rgba(255, 255, 255, 0.02); +} + +.router-dialog .p-inputtextarea{ + min-height: 11rem; +} + +.router-dialog-actions{ + justify-content: space-between; + gap: 0.75rem; + padding-top: 0.1rem; +} + +.router-dialog-actions .p-button{ + min-width: 11rem; +} + +@media (max-width: 720px) { + .router-dialog .p-dialog-header{ + padding: 1rem 0.85rem 0.85rem; + } + + .router-dialog .p-dialog-content{ + padding: 0 0.85rem 0.95rem; + } + + .router-dialog-header{ + align-items: flex-start; + } + + .router-dialog-panel__header{ + flex-direction: column; + } + + .router-dialog-actions{ + flex-direction: column-reverse; + align-items: stretch; + } + + .router-dialog-actions .p-button{ + width: 100%; + min-width: 0; + } +} + + +/* PrimeNG v20 compatibility bridge */ +.p-select, +.p-dropdown, +.p-multiselect, +.p-inputtext, +.p-textarea, +.p-inputtextarea, +textarea.p-inputtextarea, +textarea.p-textarea { + width: 100%; +} + +.p-button:not(.p-button-secondary):not(.p-button-help):not(.p-button-danger):not(.p-button-text):not(.p-button-outlined) { + background: var(--primary); + border-color: var(--primary); + color: #f8fbff; +} + +body.dark-theme .p-button:not(.p-button-secondary):not(.p-button-help):not(.p-button-danger):not(.p-button-text):not(.p-button-outlined) { + background: var(--primary); + border-color: var(--primary); + color: #17212b; +} + +.p-select, +.p-dropdown, +.p-textarea, +.p-inputtextarea, +.p-inputtext, +textarea.p-inputtextarea, +textarea.p-textarea { + border-radius: 12px; + border: 1px solid var(--border-strong); + background: color-mix(in srgb, var(--surface-1) 90%, white 10%); + color: var(--text-main); + box-shadow: none; +} + +body.dark-theme .p-select, +body.dark-theme .p-dropdown, +body.dark-theme .p-textarea, +body.dark-theme .p-inputtextarea, +body.dark-theme .p-inputtext, +body.dark-theme textarea.p-inputtextarea, +body.dark-theme textarea.p-textarea { + background: rgba(255, 255, 255, 0.03); + color: var(--text-main); + border-color: var(--border-color); +} + +.p-select, +.p-dropdown { + display: flex; + align-items: center; + min-height: 2.95rem; +} + +.p-select .p-select-label, +.p-select .p-select-label.p-placeholder, +.p-dropdown .p-dropdown-label, +.p-dropdown .p-dropdown-label.p-inputtext, +.p-dropdown .p-dropdown-label.p-placeholder, +.p-multiselect .p-multiselect-label, +.p-multiselect .p-multiselect-label.p-placeholder { + width: 100%; + min-height: 0; + padding: 0.78rem 0 0.78rem 1rem; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + display: flex; + align-items: center; + font-family: var(--font-body); + font-size: 0.96rem; + font-weight: 500; + line-height: 1.35; + letter-spacing: 0; + text-transform: none; + color: var(--text-main); +} + +.p-select .p-select-label.p-placeholder, +.p-dropdown .p-dropdown-label.p-placeholder, +.p-multiselect .p-multiselect-label.p-placeholder { + font-weight: 400; + color: var(--text-soft); +} + +.repository-toolbar .p-select .p-select-label, +.repository-compare .p-select .p-select-label, +.diff-configs-compare .p-select .p-select-label, +.repository-toolbar .p-dropdown .p-dropdown-label, +.repository-compare .p-dropdown .p-dropdown-label { + font-size: 0.95rem; + line-height: 1.3; +} + +.p-select .p-select-dropdown, +.p-dropdown .p-dropdown-trigger, +.p-multiselect .p-multiselect-trigger { + width: 2.85rem; + display: grid; + place-items: center; + align-self: stretch; + color: var(--text-soft); + flex: 0 0 2.85rem; +} + +.p-select.p-focus, +.p-dropdown:not(.p-disabled).p-focus, +.p-inputtext:enabled:focus, +.p-textarea:enabled:focus, +.p-inputtextarea:enabled:focus, +textarea.p-inputtextarea:enabled:focus, +textarea.p-textarea:enabled:focus { + border-color: color-mix(in srgb, var(--blue) 72%, var(--border-strong)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 18%, transparent); +} + +body.dark-theme .auth-card .p-inputtext, +body.dark-theme .auth-card .p-password-input, +body.dark-theme .auth-card .p-select, +body.dark-theme .auth-card .p-textarea, +body.dark-theme .auth-card .p-inputtextarea { + color: var(--text-main); + border-color: var(--border-color); + background: rgba(255, 255, 255, 0.035); +} + +body.dark-theme .auth-card .p-inputtext::placeholder, +body.dark-theme .auth-card .p-password-input::placeholder, +body.dark-theme .auth-card .p-textarea::placeholder, +body.dark-theme .auth-card .p-inputtextarea::placeholder { + color: var(--text-soft); +} + +body.dark-theme .auth-card .p-inputtext:enabled:focus, +body.dark-theme .auth-card .p-password-input:enabled:focus, +body.dark-theme .auth-card .p-textarea:enabled:focus, +body.dark-theme .auth-card .p-inputtextarea:enabled:focus { + border-color: var(--blue); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 18%, transparent); +} + +.p-select-overlay, +.p-dropdown-panel, +.p-multiselect-panel, +.p-component-overlay { + backdrop-filter: blur(12px); + background: color-mix(in srgb, var(--surface-1) 97%, white 3%); + border: 1px solid var(--border-color); + color: var(--text-main); + box-shadow: var(--shadow-lg); +} + +body.dark-theme .p-select-overlay, +body.dark-theme .p-dropdown-panel, +body.dark-theme .p-multiselect-panel, +body.dark-theme .p-component-overlay { + background: #1f2935; + border-color: rgba(146, 170, 194, 0.2); +} + +.p-select-list, +.p-dropdown-panel .p-dropdown-items, +.p-multiselect-panel .p-multiselect-items { + padding: 0.35rem; + background: transparent; +} + +body.dark-theme .p-select-list, +body.dark-theme .p-dropdown-panel .p-dropdown-items, +body.dark-theme .p-multiselect-panel .p-multiselect-items { + background: transparent; +} + +.p-select-option, +.p-dropdown-panel .p-dropdown-item, +.p-multiselect-panel .p-multiselect-item { + border-radius: 10px; + font-size: 0.92rem; + line-height: 1.35; + color: var(--text-main); + transition: background-color 0.12s ease, color 0.12s ease; +} + +.p-select-option.p-focus, +.p-select-option:hover, +.p-dropdown-panel .p-dropdown-item.p-focus, +.p-dropdown-panel .p-dropdown-item:hover, +.p-multiselect-panel .p-multiselect-item.p-focus, +.p-multiselect-panel .p-multiselect-item:hover { + background: #dbe5f0; + color: #13202c; +} + +.p-select-option.p-select-option-selected, +.p-dropdown-panel .p-dropdown-item.p-highlight, +.p-multiselect-panel .p-multiselect-item.p-highlight { + background: #c8d8ea; + color: #13202c; + font-weight: 600; +} + +body.dark-theme .p-select-option, +body.dark-theme .p-dropdown-panel .p-dropdown-item, +body.dark-theme .p-multiselect-panel .p-multiselect-item { + color: #dbe5ef; +} + +body.dark-theme .p-select-option.p-focus, +body.dark-theme .p-select-option:hover, +body.dark-theme .p-dropdown-panel .p-dropdown-item.p-focus, +body.dark-theme .p-dropdown-panel .p-dropdown-item:hover, +body.dark-theme .p-multiselect-panel .p-multiselect-item.p-focus, +body.dark-theme .p-multiselect-panel .p-multiselect-item:hover { + background: #314355; + color: #f4f8fb; +} + +body.dark-theme .p-select-option.p-select-option-selected, +body.dark-theme .p-dropdown-panel .p-dropdown-item.p-highlight, +body.dark-theme .p-multiselect-panel .p-multiselect-item.p-highlight { + background: #d9e1ea; + color: #13202c; + font-weight: 600; +} + +.p-textarea, +.p-inputtextarea, +textarea.p-textarea, +textarea.p-inputtextarea { + padding: 0.82rem 0.9rem; +} + +.router-dialog.p-dialog { + border: 1px solid var(--border-color); + background: var(--surface-1); + box-shadow: var(--shadow-lg); +} + +body.dark-theme .router-dialog.p-dialog { + border-color: color-mix(in srgb, var(--border-color) 65%, transparent); +} + +.router-dialog .p-dialog-header { + padding: 1.1rem 1.2rem 0.95rem; + background: linear-gradient(180deg, color-mix(in srgb, var(--surface-1) 98%, transparent), color-mix(in srgb, var(--surface-0) 98%, transparent)); + border-bottom: 1px solid var(--border-color); +} + +.router-dialog .p-dialog-content { + padding: 0 1.2rem 1.2rem; + background: color-mix(in srgb, var(--surface-0) 98%, transparent); +} + +.router-dialog .p-dialog-header-icons { + align-self: flex-start; + margin-top: 0.2rem; +} + +.router-dialog-panel { + background: color-mix(in srgb, var(--surface-0) 96%, transparent); + border: 1px solid var(--border-color); + box-shadow: none; +} + +.router-dialog .p-select, +.router-dialog .p-textarea, +.router-dialog .p-inputtextarea, +.router-dialog .p-inputtext { + background: color-mix(in srgb, var(--surface-1) 90%, transparent); +} + +body.dark-theme .router-dialog .p-select, +body.dark-theme .router-dialog .p-textarea, +body.dark-theme .router-dialog .p-inputtextarea, +body.dark-theme .router-dialog .p-inputtext, +body.dark-theme .router-dialog-panel { + background: rgba(255, 255, 255, 0.03); + border-color: var(--border-color); +} + +.repository-toolbar__search .p-input-icon-left { + position: relative; + display: block; + width: 100%; +} + +.repository-toolbar__search .p-input-icon-left > i { + position: absolute; + top: 50%; + left: 0.9rem; + transform: translateY(-50%); + margin: 0; + z-index: 1; + color: var(--text-soft); + pointer-events: none; + line-height: 1; +} + +.repository-toolbar__search .p-input-icon-left > .p-inputtext { + display: block; + width: 100%; + min-height: 2.75rem; + padding-left: 2.5rem; +} + + +body.dark-theme .p-toast .p-toast-summary, +body.dark-theme .p-toast .p-toast-detail, +body.dark-theme .p-toast .p-toast-message-icon, +body.dark-theme .p-toast .p-toast-icon-close { + color: var(--text-main); +} + +.repository-table .p-paginator .p-paginator-pages .p-paginator-page, +.repository-table .p-paginator .p-paginator-next, +.repository-table .p-paginator .p-paginator-prev, +.repository-table .p-paginator .p-select { + min-width: 2rem; + height: 2rem; +} \ No newline at end of file diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..fa026e8 --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] +} \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..44011b8 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "strictTemplates": true + } +} \ No newline at end of file diff --git a/make_zip.py b/make_zip.py new file mode 100644 index 0000000..e133aad --- /dev/null +++ b/make_zip.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +import os +import sys +import zipfile +import subprocess +from pathlib import Path + + +def run_git_command(args, repo_path: Path) -> bytes: + result = subprocess.run( + ["git", *args], + cwd=repo_path, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + return result.stdout + + +def get_files_to_archive(repo_path: Path) -> list[str]: + output = run_git_command( + ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], + repo_path, + ) + files = output.decode("utf-8", errors="surrogateescape").split("\0") + return [f for f in files if f] + + +def make_zip(repo_path: Path, output_zip: Path) -> None: + files = get_files_to_archive(repo_path) + + output_zip = output_zip.resolve() + if output_zip.exists(): + output_zip.unlink() + + with zipfile.ZipFile(output_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf: + for rel_path in files: + abs_path = repo_path / rel_path + + if not abs_path.exists(): + continue + + if abs_path.resolve() == output_zip: + continue + + zf.write(abs_path, arcname=rel_path) + + print(f"Utworzono archiwum: {output_zip}") + print(f"Dodano plików: {len(files)}") + + +def main(): + repo_path = Path.cwd() + + if len(sys.argv) > 1: + output_zip = Path(sys.argv[1]) + else: + output_zip = repo_path / f"{repo_path.name}.zip" + + try: + run_git_command(["rev-parse", "--show-toplevel"], repo_path) + except subprocess.CalledProcessError: + print("Błąd: ten katalog nie jest repozytorium Git.", file=sys.stderr) + sys.exit(1) + + make_zip(repo_path, output_zip) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/reverse-proxy/Dockerfile b/reverse-proxy/Dockerfile new file mode 100644 index 0000000..3a741b8 --- /dev/null +++ b/reverse-proxy/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:mainline +COPY reverse-proxy/default.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/reverse-proxy/default.conf b/reverse-proxy/default.conf new file mode 100644 index 0000000..7aa9fa9 --- /dev/null +++ b/reverse-proxy/default.conf @@ -0,0 +1,60 @@ +upstream frontend_upstream { + server frontend:80; + keepalive 16; +} + +upstream backend_upstream { + server backend:8000; + keepalive 16; +} + +server { + listen 80; + server_name _; + + server_tokens off; + + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Connection ""; + proxy_read_timeout 60s; + proxy_send_timeout 60s; + + location ^~ /api/ { + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + proxy_pass http://backend_upstream/api/; + } + + location = /docs { + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + proxy_pass http://backend_upstream/docs; + } + + location = /openapi.json { + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + proxy_pass http://backend_upstream/openapi.json; + } + + location = /redoc { + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + proxy_pass http://backend_upstream/redoc; + } + + location ~* \.(?:css|js|mjs|png|jpg|jpeg|gif|svg|ico|webp|woff2?)$ { + proxy_pass http://frontend_upstream; + add_header Cache-Control "public, max-age=31536000, immutable" always; + } + + location / { + proxy_pass http://frontend_upstream; + add_header Cache-Control "no-store, no-cache" always; + } +} diff --git a/start_dev.sh b/start_dev.sh new file mode 100755 index 0000000..7007004 --- /dev/null +++ b/start_dev.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BACKEND_DIR="${ROOT_DIR}/backend" +FRONTEND_DIR="${ROOT_DIR}/frontend" +BACKEND_VENV="${BACKEND_DIR}/.venv" +PYTHON_BIN="${PYTHON_BIN:-python3}" +ENV_FILE="${BACKEND_DIR}/.env" +ENV_EXAMPLE_FILE="${BACKEND_DIR}/.env.dev.example" +BACKEND_LOG="/tmp/routeros-next-backend.log" + +cleanup() { + if [[ -n "${BACKEND_PID:-}" ]] && kill -0 "${BACKEND_PID}" 2>/dev/null; then + kill "${BACKEND_PID}" 2>/dev/null || true + fi +} +trap cleanup EXIT INT TERM + +read_env_value() { + local key="$1" + local file="$2" + if [[ -f "$file" ]]; then + local line + line=$(grep -E "^${key}=" "$file" | tail -n 1 || true) + if [[ -n "$line" ]]; then + echo "${line#*=}" + return 0 + fi + fi + return 1 +} + +if [[ ! -d "${BACKEND_VENV}" ]]; then + "${PYTHON_BIN}" -m venv "${BACKEND_VENV}" +fi + +# shellcheck disable=SC1091 +source "${BACKEND_VENV}/bin/activate" +python -m pip install --upgrade pip +python -m pip install -r "${BACKEND_DIR}/requirements.txt" + +if [[ ! -f "${ENV_FILE}" ]] && [[ -f "${ENV_EXAMPLE_FILE}" ]]; then + cp "${ENV_EXAMPLE_FILE}" "${ENV_FILE}" +fi + +DEFAULT_ADMIN_USERNAME="$(read_env_value DEFAULT_ADMIN_USERNAME "${ENV_FILE}" || read_env_value DEFAULT_ADMIN_USERNAME "${ENV_EXAMPLE_FILE}" || echo admin)" +DEFAULT_ADMIN_PASSWORD="$(read_env_value DEFAULT_ADMIN_PASSWORD "${ENV_FILE}" || read_env_value DEFAULT_ADMIN_PASSWORD "${ENV_EXAMPLE_FILE}" || echo admin)" + +cd "${BACKEND_DIR}" +PYTHONPATH="${BACKEND_DIR}" uvicorn app.main:app --reload --reload-dir app --host 127.0.0.1 --port 8000 > "${BACKEND_LOG}" 2>&1 & +BACKEND_PID=$! + +for _ in $(seq 1 30); do + if python - <<'PY' +import sys +from urllib.request import urlopen +try: + with urlopen('http://127.0.0.1:8000/api/health', timeout=1) as response: + sys.exit(0 if response.status == 200 else 1) +except Exception: + sys.exit(1) +PY + then + break + fi + sleep 1 +done + +if ! python - <<'PY' +import sys +from urllib.request import urlopen +try: + with urlopen('http://127.0.0.1:8000/api/health', timeout=1) as response: + sys.exit(0 if response.status == 200 else 1) +except Exception: + sys.exit(1) +PY +then + echo "Backend failed to become ready. Log: ${BACKEND_LOG}" + tail -n 100 "${BACKEND_LOG}" || true + exit 1 +fi + +echo "Backend: http://127.0.0.1:8000" +echo "API docs: http://127.0.0.1:8000/docs" +echo "Frontend: http://127.0.0.1:4200" +echo "Default login: ${DEFAULT_ADMIN_USERNAME} / ${DEFAULT_ADMIN_PASSWORD}" + +cd "${FRONTEND_DIR}" +if [[ ! -d node_modules ]]; then + npm install +fi + +npm run start diff --git a/start_prod.sh b/start_prod.sh new file mode 100755 index 0000000..11dec12 --- /dev/null +++ b/start_prod.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${ROOT_DIR}" + +if [[ ! -f .env ]] && [[ -f .env.example ]]; then + cp .env.example .env +fi + +if grep -q '^DEFAULT_ADMIN_PASSWORD=admin$' .env 2>/dev/null; then + echo "Warning: DEFAULT_ADMIN_PASSWORD is still set to admin in .env" +fi + +docker compose up --build