diff --git a/.dockerignore b/.dockerignore index e2c608c2..7aaadbd9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,8 @@ .github *.md tests/ -docs/ \ No newline at end of file +docs/ +src/db +src/wg-dashboard.ini +src/static/app +src/static/client diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 178189e3..27e1e3d0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,6 +16,12 @@ updates: target-branch: "development" schedule: interval: "weekly" + + - package-ecosystem: "npm" + directory: "/src/static/client" + target-branch: "development" + schedule: + interval: "weekly" - package-ecosystem: "github-actions" directory: "/.github" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1cc04926..d6594fef 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -104,7 +104,6 @@ jobs: only-fixed: true write-comment: true github-token: ${{ secrets.GITHUB_TOKEN }} - exit-code: true - name: Docker Scout Compare uses: docker/scout-action@v1 diff --git a/README.md b/README.md index 8e76da71..eddc2dcf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ > 🎉 To help us better understand and improve WGDashboard’s performance, we’re launching the **WGDashboard Testing Program**. As part of this program, participants will receive free WireGuard VPN access to our server in Toronto, Canada, valid for **24 hours** or up to **1GB of total traffic**—whichever comes first. If you’d like to join, visit [https://wg.wgdashboard.dev/](https://wg.wgdashboard.dev/) for more details! - ![](https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Posters/Banner.png) diff --git a/docker/Dockerfile b/docker/Dockerfile index e0b0b3b4..b6630c23 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ # # Pull the current golang-alpine image. -FROM golang:1.25-alpine AS awg-go +FROM golang:1.26-alpine3.23 AS awg-go # Install build-dependencies. RUN apk add --no-cache \ @@ -30,7 +30,7 @@ RUN go version && \ # AWG TOOLS BUILDING STAGE # Base: Alpine # -FROM alpine:latest AS awg-tools +FROM alpine:3.23 AS awg-tools # Install needed dependencies. RUN apk add --no-cache \ @@ -55,7 +55,7 @@ RUN make && chmod +x wg* # # Use the python-alpine image for building pip dependencies -FROM python:3.14-alpine AS pip-builder +FROM python:3.14-alpine3.23 AS pip-builder ARG TARGETPLATFORM @@ -91,7 +91,7 @@ RUN . /opt/wgdashboard/src/venv/bin/activate && \ # # Running with the python-alpine image. -FROM python:3.14-alpine AS final +FROM python:3.14-alpine3.23 AS final LABEL maintainer="dselen@nerthus.nl" # Install only the runtime dependencies @@ -114,15 +114,18 @@ ENV TZ="Europe/Amsterdam" \ global_dns="9.9.9.9" \ wgd_port="10086" \ public_ip="" \ - WGDASH=/opt/wgdashboard + WGDASH=/opt/wgdashboard \ + dynamic_config="true" # Create directories needed for operation -RUN mkdir /data /configs -p ${WGDASH}/src /etc/amnezia/amneziawg +RUN mkdir /data /configs -p ${WGDASH}/src /etc/amnezia/amneziawg \ + && echo "name_servers=${global_dns}" >> /etc/resolvconf.conf # Copy the venv and source files from local compiled locations or repos COPY ./src ${WGDASH}/src COPY --from=pip-builder /opt/wgdashboard/src/venv /opt/wgdashboard/src/venv COPY ./docker/wg0.conf.template /tmp/wg0.conf.template +COPY ./docker/wg-dashboard-oidc-providers.json.template /tmp/wg-dashboard-oidc-providers.json.template # Copy in the runtime script, essential. COPY ./docker/entrypoint.sh /entrypoint.sh diff --git a/docker/README.md b/docker/README.md index 9a39a07c..b6966712 100644 --- a/docker/README.md +++ b/docker/README.md @@ -23,7 +23,7 @@ To get the container running you either pull the pre-made image from a remote re - ghcr.io/wgdashboard/wgdashboard: - docker.io/donaldzou/wgdashboard: -> tags should be either: latest, main, or . +> tags should be either: latest, main, , (if built) or . From there either use the environment variables described below as parameters or use the Docker Compose file: `compose.yaml`.
Be careful, the default generated WireGuard configuration file uses port 51820/udp. So make sure to use this port if you want to use it out of the box.
@@ -95,23 +95,29 @@ Updating the WGDashboard container should be through 'The Docker Way' - by pulli ## ⚙️ Environment Variables -| Variable | Accepted Values | Default | Example | Description | -| ------------------ | ---------------------------------------- | ----------------------- | ------------------------ | ----------------------------------------------------------------------- | -| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. | -| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. | -| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NAT’d. | -| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. | -| `username` | Any non‐empty string | `-` | `admin` | Username for the WGDashboard web interface account. | -| `password` | Any non‐empty string | `-` | `s3cr3tP@ss` | Password for the WGDashboard web interface account (stored hashed). | -| `enable_totp` | `true`, `false` | `true` | `false` | Enable TOTP‐based two‐factor authentication for the account. | -| `wg_autostart` | Wireguard interface name | `-` | `wg0` or `wg0\|\|wg1\|\|wg2` | Auto‐start the WireGuard interface when the container launches. | -| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. | -| `email_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. | -| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. | -| `email_username` | Any non-empty string | `-` | `user@example.com` | Username for SMTP authentication. | -| `email_password` | Any non-empty string | `-` | `app_password` | Password for SMTP authentication. | -| `email_from` | Valid email address | `-` | `noreply@example.com` | Email address used as the sender for notifications. | -| `email_template` | Path to template file | `-` | `your-template` | Custom template for email notifications. | +| Variable | Accepted Values | Default | Example | Description | +| ------------------ | ---------------------------------------- | ----------------------- | --------------------- | ----------------------------------------------------------------------- | +| `dynamic_config` | true, yes, false, no | `true` | `true` or `no` | Turns on or off the dynamic configuration feature, on by default for Docker | +| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. | +| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. | +| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NAT’d. | +| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. | +| `username` | Any non‐empty string | `-` | `admin` | Username for the WGDashboard web interface account. | +| `password` | Any non‐empty string | `-` | `s3cr3tP@ss` | Password for the WGDashboard web interface account (stored hashed). | +| `enable_totp` | `true`, `false` | `true` | `false` | Enable TOTP‐based two‐factor authentication for the account. | +| `wg_autostart` | Wireguard interface name | `false` | `true` | Auto‐start the WireGuard client when the container launches. | +| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. | +| `email_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. | +| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. | +| `email_username` | Any non-empty string | `-` | `user@example.com` | Username for SMTP authentication. | +| `email_password` | Any non-empty string | `-` | `app_password` | Password for SMTP authentication. | +| `email_from` | Valid email address | `-` | `noreply@example.com` | Email address used as the sender for notifications. | +| `email_template` | Path to template file | `-` | `your-template` | Custom template for email notifications. | +| `database_type` | `sqlite`, `postgresql`, `mariadb+mariadbconnector`, etc. | `-` | `postgresql` | Type of [sqlalchemy database engine](https://docs.sqlalchemy.org/en/21/core/engines.html). | +| `database_host` | Any non-empty string | `-` | `localhost` | IP-Address or hostname of the SQL-database server. | +| `database_port` | Any non-empty string (or int for port) | `-` | `5432` | Port for the database communication. | +| `database_username`| Valid database username | `-` | `database_user` | Database user username. | +| `database_password`| Valid database password | `-` | `database_password` | Database user password. | --- diff --git a/docker/compose.yaml b/docker/compose.yaml index d8f2eac9..445c3102 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -13,6 +13,7 @@ services: # By default its all disabled, but uncomment the following lines to apply these. (uncommenting is removing the # character) # Refer to the documentation on https://wgdashboard.dev/ for more info on what everything means. #environment: + #- wg_autostart=wg0 #- tz= # <--- Set container timezone, default: Europe/Amsterdam. #- public_ip= # <--- Set public IP to ensure the correct one is chosen, defaulting to the IP give by ifconfig.me. #- wgd_port= # <--- Set the port WGDashboard will use for its web-server. diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 7a024505..fe2d2dff 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -85,8 +85,6 @@ echo "------------------------- START ----------------------------" echo "Starting the WGDashboard Docker container." ensure_installation() { - echo "Quick-installing..." - # Make the wgd.sh script executable. chmod +x "${WGDASH}"/src/wgd.sh cd "${WGDASH}"/src || exit @@ -102,23 +100,51 @@ ensure_installation() { echo "Removing clear command from wgd.sh for better Docker logging." sed -i '/clear/d' ./wgd.sh + # PERSISTENCE FOR databases directory # Create required directories and links if [ ! -d "/data/db" ]; then echo "Creating database dir" mkdir -p /data/db fi - if [ ! -d "${WGDASH}/src/db" ]; then - ln -s /data/db "${WGDASH}/src/db" + if [[ ! -L "${WGDASH}/src/db" ]] && [[ -d "${WGDASH}/src/db" ]]; then + echo "Removing ${WGDASH}/src/db since its not a symbolic link." + rm -rfv "${WGDASH}/src/db" + fi + if [[ -L "${WGDASH}/src/db" ]]; then + echo "${WGDASH}/src/db is a symbolic link." + else + ln -sv /data/db "${WGDASH}/src/db" fi + # PERSISTENCE FOR wg-dashboard-oidc-providers.json + if [ ! -f "/data/wg-dashboard-oidc-providers.json" ]; then + echo "Creating wg-dashboard-oidc-providers.json file" + cp -v /tmp/wg-dashboard-oidc-providers.json.template /data/wg-dashboard-oidc-providers.json + fi + if [[ ! -L "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]] && [[ -f "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]]; then + echo "Removing ${WGDASH}/src/wg-dashboard-oidc-providers.json since its not a symbolic link." + rm -fv "${WGDASH}/src/wg-dashboard-oidc-providers.json" + fi + if [[ -L "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]]; then + echo "${WGDASH}/src/wg-dashboard-oidc-providers.json is a symbolic link." + else + ln -sv /data/wg-dashboard-oidc-providers.json "${WGDASH}/src/wg-dashboard-oidc-providers.json" + fi + + # PERSISTENCE FOR wg-dashboard.ini if [ ! -f "${config_file}" ]; then echo "Creating wg-dashboard.ini file" touch "${config_file}" fi - - if [ ! -f "${WGDASH}/src/wg-dashboard.ini" ]; then - ln -s "${config_file}" "${WGDASH}/src/wg-dashboard.ini" + if [[ ! -L "${WGDASH}/src/wg-dashboard.ini" ]] && [[ -f "${WGDASH}/src/wg-dashboard.ini" ]]; then + echo "Removing ${WGDASH}/src/wg-dashboard.ini since its not a symbolic link." + rm -fv "${WGDASH}/src/wg-dashboard.ini" + fi + if [[ -L "${WGDASH}/src/wg-dashboard.ini" ]]; then + echo "${WGDASH}/src/wg-dashboard.ini is a symbolic link." + else + ln -sv "${config_file}" "${WGDASH}/src/wg-dashboard.ini" fi # Setup WireGuard if needed @@ -142,14 +168,25 @@ set_envvars() { # Check if config file is empty if [ ! -s "${config_file}" ]; then echo "Config file is empty. Creating initial structure." + elif [[ ${dynamic_config,,} =~ ^(false|no)$ ]]; then + echo "Dynamic configuration feature turned off, not changing anything" + return fi echo "Checking basic configuration:" set_ini Peers peer_global_dns "${global_dns}" if [ -z "${public_ip}" ]; then - public_ip=$(curl -s ifconfig.me) - echo "Automatically detected public IP: ${public_ip}" + public_ip=$(curl -s https://ifconfig.me) + if [ -z "${public_ip}" ]; then + echo "Using fallback public IP resolution website" + public_ip=$(curl -s https://api.ipify.org) + fi + if [ -z "${public_ip}" ]; then + echo "Failed to resolve publicly. Using private address." + public_ip=$(hostname -i) + fi + echo "Automatically detected public IP: ${public_ip}" fi set_ini Peers remote_endpoint "${public_ip}" @@ -183,6 +220,24 @@ set_envvars() { set_ini WireGuardConfiguration autostart "${wg_autostart}" fi + # Database (check if any settings need to be configured) + database_vars=("database_type" "database_host" "database_port" "database_username" "database_password") + for var in "${database_vars[@]}"; do + if [ -n "${!var}" ]; then + echo "Configuring database settings:" + break + fi + done + + # Database (iterate through all possible fields) + database_fields=("type:database_type" "host:database_host" "port:database_port" + "username:database_username" "password:database_password") + + for field_pair in "${database_fields[@]}"; do + IFS=: read -r field var <<< "$field_pair" + [[ -n "${!var}" ]] && set_ini Database "$field" "${!var}" + done + # Email (check if any settings need to be configured) email_vars=("email_server" "email_port" "email_encryption" "email_username" "email_password" "email_from" "email_template") for var in "${email_vars[@]}"; do @@ -207,6 +262,9 @@ set_envvars() { start_and_monitor() { printf "\n---------------------- STARTING CORE -----------------------\n" + # Due to resolvconf resetting the DNS we echo back the one we defined (or fallback to default). + resolvconf -u + # Due to some instances complaining about this, making sure its there every time. mkdir -p /dev/net mknod /dev/net/tun c 10 200 @@ -220,7 +278,9 @@ start_and_monitor() { ${WGDASH}/src/venv/bin/gunicorn --config ${WGDASH}/src/gunicorn.conf.py + cp /etc/resolv.conf /etc/resolv.conf.docker /usr/sbin/resolvconf -u + cat /etc/resolv.conf.docker | resolvconf -a docker.inet if [ $? -ne 0 ]; then echo "Loading WGDashboard failed... Look above for details." diff --git a/docker/wg-dashboard-oidc-providers.json.template b/docker/wg-dashboard-oidc-providers.json.template new file mode 100644 index 00000000..3764d0d2 --- /dev/null +++ b/docker/wg-dashboard-oidc-providers.json.template @@ -0,0 +1,16 @@ +{ + "Admin": { + "Provider": { + "client_id": "", + "client_secret": "", + "issuer": "" + } + }, + "Client": { + "Provider": { + "client_id": "", + "client_secret": "", + "issuer": "" + } + } +} diff --git a/src/client.py b/src/client.py index cc7ac218..a637d13f 100644 --- a/src/client.py +++ b/src/client.py @@ -4,8 +4,9 @@ from tzlocal import get_localzone from functools import wraps -from flask import Blueprint, render_template, abort, request, Flask, current_app, session, redirect, url_for +from flask import Blueprint, render_template, abort, request, Flask, current_app, session, redirect, url_for, send_from_directory import os +import mimetypes from modules.WireguardConfiguration import WireguardConfiguration from modules.DashboardConfig import DashboardConfig @@ -53,6 +54,8 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration], @client.post(f'{prefix}/api/signup') def ClientAPI_SignUp(): + if not dashboardConfig.GetConfig("Clients", "sign_up")[1]: + abort(404) data = request.get_json() status, msg = dashboardClients.SignUp(**data) return ResponseObject(status, msg) @@ -97,7 +100,7 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration], date = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC') emailSender = EmailSender(dashboardConfig) - if not emailSender.ready(): + if not emailSender.is_ready(): return ResponseObject(False, "We can't send you an email due to your Administrator has not setup email service. Please contact your administrator.") data = request.get_json() @@ -192,14 +195,26 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration], }) return ResponseObject(status, msg) + @client.get(f'{prefix}/assets/') + @client.get(f'{prefix}/img/') + def serve_client_static(filename): + client_dist_folder = os.path.abspath("./static/dist/WGDashboardClient") + mimetype = mimetypes.guess_type(filename)[0] + subfolder = 'assets' if 'assets' in request.path else 'img' + return send_from_directory(os.path.join(client_dist_folder, subfolder), os.path.basename(filename), mimetype=mimetype) + @client.get(prefix) def ClientIndex(): - return render_template('client.html') + app_prefix = dashboardConfig.GetConfig("Server", "app_prefix")[1] + return render_template('client.html', APP_PREFIX=app_prefix) @client.get(f'{prefix}/api/serverInformation') def ClientAPI_ServerInformation(): return ResponseObject(data={ - "ServerTimezone": str(get_localzone()) + "ServerTimezone": str(get_localzone()), + "SignUp": { + "enable": dashboardConfig.GetConfig("Clients", "sign_up")[1] + } }) @client.get(f'{prefix}/api/validateAuthentication') @@ -229,4 +244,4 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration], return ResponseObject(status, message) - return client \ No newline at end of file + return client diff --git a/src/dashboard.py b/src/dashboard.py index ff718a15..e4d4eefb 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta import sqlalchemy from jinja2 import Template -from flask import Flask, request, render_template, session, send_file +from flask import Flask, request, render_template, session, send_file, current_app from flask_cors import CORS from icmplib import ping, traceroute from flask.json.provider import DefaultJSONProvider @@ -17,8 +17,7 @@ from itertools import islice from sqlalchemy import RowMapping from modules.Utilities import ( - RegexMatch, StringToBoolean, - ValidateIPAddressesWithRange, ValidateDNSAddress, + RegexMatch, StringToBoolean, ValidateDNSAddress, GenerateWireguardPublicKey, GenerateWireguardPrivateKey ) from packaging import version @@ -30,7 +29,7 @@ from modules.PeerShareLinks import PeerShareLinks from modules.PeerJobs import PeerJobs from modules.DashboardConfig import DashboardConfig from modules.WireguardConfiguration import WireguardConfiguration -from modules.AmneziaWireguardConfiguration import AmneziaWireguardConfiguration +from modules.AmneziaConfiguration import AmneziaConfiguration from client import createClientBlueprint @@ -72,7 +71,11 @@ def ResponseObject(status=True, message=None, data=None, status_code = 200) -> F ''' Flask App ''' -app = Flask("WGDashboard", template_folder=os.path.abspath("./static/dist/WGDashboardAdmin")) +_, APP_PREFIX_INIT = DashboardConfig().GetConfig("Server", "app_prefix") +app = Flask("WGDashboard", + template_folder=os.path.abspath("./static/dist/WGDashboardAdmin"), + static_folder=os.path.abspath("./static/dist/WGDashboardAdmin"), + static_url_path=APP_PREFIX_INIT if APP_PREFIX_INIT else '') def peerInformationBackgroundThread(): global WireguardConfigurations @@ -92,11 +95,13 @@ def peerInformationBackgroundThread(): c.getPeersTransfer() c.getPeersEndpoint() c.getPeers() - if delay == 6: - if c.configurationInfo.PeerTrafficTracking: - c.logPeersTraffic() - if c.configurationInfo.PeerHistoricalEndpointTracking: - c.logPeersHistoryEndpoint() + if DashboardConfig.GetConfig('WireGuardConfiguration', 'peer_tracking')[1] is True: + print("[WGDashboard] Tracking Peers") + if delay == 6: + if c.configurationInfo.PeerTrafficTracking: + c.logPeersTraffic() + if c.configurationInfo.PeerHistoricalEndpointTracking: + c.logPeersHistoryEndpoint() c.getRestrictedPeersList() except Exception as e: app.logger.error(f"[WGDashboard] Background Thread #1 Error", e) @@ -161,10 +166,10 @@ def InitWireguardConfigurationsList(startup: bool = False): if i in WireguardConfigurations.keys(): if WireguardConfigurations[i].configurationFileChanged(): with app.app_context(): - WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i) + WireguardConfigurations[i] = AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i) else: with app.app_context(): - WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i, startup=startup) + WireguardConfigurations[i] = AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i, startup=startup) except WireguardConfiguration.InvalidConfigurationFileException as e: app.logger.error(f"{i} have an invalid configuration file.") @@ -264,7 +269,9 @@ def auth_req(): ("username" not in session or session.get("role") != "admin") and (f"{appPrefix}/" != request.path and f"{appPrefix}" != request.path) and not request.path.startswith(f'{appPrefix}/client') - and not request.path.startswith(f'{appPrefix}/static') + and not request.path.startswith(f'{appPrefix}/img') + and not request.path.startswith(f'{appPrefix}/json') + and not request.path.startswith(f'{appPrefix}/assets') and request.path not in whiteList ): response = Flask.make_response(app, { @@ -422,11 +429,11 @@ def API_addWireguardConfiguration(): ) WireguardConfigurations[data['ConfigurationName']] = ( WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else ( - AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName'])) + AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName'])) else: WireguardConfigurations[data['ConfigurationName']] = ( WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) if data.get('Protocol') == 'wg' else ( - AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) + AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) return ResponseObject() @app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration') @@ -523,7 +530,7 @@ def API_renameWireguardConfiguration(): status, message = rc.renameConfiguration(data.get("NewConfigurationName")) if status: - WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName"))) + WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName"))) else: WireguardConfigurations[data.get("ConfigurationName")] = rc return ResponseObject(status, message) @@ -562,8 +569,8 @@ def API_getAllWireguardConfigurationBackup(): files.sort(key=lambda x: x[1], reverse=True) for f, ct in files: - if RegexMatch(r"^(.*)_(.*)\.(conf)$", f): - s = re.search(r"^(.*)_(.*)\.(conf)$", f) + if RegexMatch(r"^(.+)_(\d+)\.(conf)$", f): + s = re.search(r"^(.+)_(\d+)\.(conf)$", f) name = s.group(1) if name not in existingConfiguration: if name not in data['NonExistingConfigurations'].keys(): @@ -706,15 +713,30 @@ def API_updatePeerSettings(configName): preshared_key = data['preshared_key'] mtu = data['mtu'] keepalive = data['keepalive'] + notes = data.get('notes', '') wireguardConfig = WireguardConfigurations[configName] foundPeer, peer = wireguardConfig.searchPeer(id) if foundPeer: if wireguardConfig.Protocol == 'wg': - status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses, - allowed_ip, endpoint_allowed_ip, mtu, keepalive) + status, msg = peer.updatePeer(name, + private_key, + preshared_key, + dns_addresses, + allowed_ip, + endpoint_allowed_ip, + mtu, + keepalive, + notes) else: - status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses, - allowed_ip, endpoint_allowed_ip, mtu, keepalive, "off") + status, msg = peer.updatePeer(name, + private_key, + preshared_key, + dns_addresses, + allowed_ip, + endpoint_allowed_ip, + mtu, + keepalive, + notes) wireguardConfig.getPeers() DashboardWebHooks.RunWebHook('peer_updated', { "configuration": wireguardConfig.Name, @@ -864,6 +886,7 @@ def API_addPeers(configName): mtu: int = data.get('mtu', None) keep_alive: int = data.get('keepalive', None) + notes: str = data.get('notes', '') preshared_key: str = data.get('preshared_key', "") if type(mtu) is not int or mtu < 0 or mtu > 1460: @@ -919,7 +942,7 @@ def API_addPeers(configName): "endpoint_allowed_ip": endpoint_allowed_ip, "mtu": mtu, "keepalive": keep_alive, - "advanced_security": "off" + "notes": "" }) if addedCount == bulkAddAmount: break @@ -962,8 +985,11 @@ def API_addPeers(configName): for i in allowed_ips: found = False for subnet in availableIps.keys(): - network = ipaddress.ip_network(subnet, False) - ap = ipaddress.ip_network(i) + try: + network = ipaddress.ip_network(subnet, False) + ap = ipaddress.ip_network(i) + except ValueError as e: + return ResponseObject(False, str(e)) if network.version == ap.version and ap.subnet_of(network): found = True @@ -981,14 +1007,13 @@ def API_addPeers(configName): "DNS": dns_addresses, "mtu": mtu, "keepalive": keep_alive, - "advanced_security": "off" + "notes": notes }] ) return ResponseObject(status=status, message=message, data=addedPeers) except Exception as e: app.logger.error("Add peers failed", e) - return ResponseObject(False, - f"Add peers failed. Reason: {message}") + return ResponseObject(False, f"Add peers failed.") return ResponseObject(False, "Configuration does not exist") @@ -1125,13 +1150,24 @@ def API_GetPeerTraffics(): @app.get(f'{APP_PREFIX}/api/getPeerTrackingTableCounts') def API_GetPeerTrackingTableCounts(): configurationName = request.args.get("configurationName") - if configurationName not in WireguardConfigurations.keys(): + if configurationName and configurationName not in WireguardConfigurations.keys(): return ResponseObject(False, "Configuration does not exist") - c = WireguardConfigurations.get(configurationName) - return ResponseObject(data={ - "TrafficTrackingTableSize": c.getTransferTableSize(), - "HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize() - }) + + if configurationName: + c = WireguardConfigurations.get(configurationName) + return ResponseObject(data={ + "TrafficTrackingTableSize": c.getTransferTableSize(), + "HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize() + }) + + d = {} + for i in WireguardConfigurations.keys(): + c = WireguardConfigurations.get(i) + d[i] = { + "TrafficTrackingTableSize": c.getTransferTableSize(), + "HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize() + } + return ResponseObject(data=d) @app.get(f'{APP_PREFIX}/api/downloadPeerTrackingTable') def API_DownloadPeerTackingTable(): @@ -1325,12 +1361,17 @@ def API_traceroute_execute(): data=json.dumps([x['ip'] for x in result])) d = r.json() for i in range(len(result)): - result[i]['geo'] = d[i] + result[i]['geo'] = d[i] + + return ResponseObject(data=result) + except Exception as e: + app.logger.error(f"Failed to gather the geolocation data: {e}") return ResponseObject(data=result, message="Failed to request IP address geolocation") - return ResponseObject(data=result) - except Exception as exp: - return ResponseObject(False, exp) + + except Exception as e: + app.logger.error(f"Failed to execute the traceroute: {e}") + return ResponseObject(data=[], message="Failed to traceroute the given parameter") else: return ResponseObject(False, "Please provide ipAddress") @@ -1446,7 +1487,7 @@ def API_Locale_Update(): @app.get(f'{APP_PREFIX}/api/email/ready') def API_Email_Ready(): - return ResponseObject(EmailSender.ready()) + return ResponseObject(EmailSender.is_ready()) @app.post(f'{APP_PREFIX}/api/email/send') def API_Email_Send(): @@ -1700,9 +1741,9 @@ Index Page @app.get(f'{APP_PREFIX}/') def index(): - return render_template('index.html') + return render_template('index.html', APP_PREFIX=APP_PREFIX) if __name__ == "__main__": startThreads() DashboardPlugins.startThreads() - app.run(host=app_ip, debug=False, port=app_port) \ No newline at end of file + app.run(host=app_ip, debug=False, port=app_port) diff --git a/src/gunicorn.conf.py b/src/gunicorn.conf.py index cba84b21..f046bb86 100644 --- a/src/gunicorn.conf.py +++ b/src/gunicorn.conf.py @@ -1,4 +1,5 @@ import dashboard +import os from datetime import datetime global sqldb, cursor, DashboardConfig, WireguardConfigurations, AllPeerJobs, JobLogger, Dash app_host, app_port = dashboard.gunicornConfig() @@ -16,7 +17,7 @@ daemon = True pidfile = './gunicorn.pid' wsgi_app = "dashboard:app" accesslog = f"./log/access_{date}.log" -loglevel = "info" +loglevel = os.environ['log_level'] if 'log_level' in os.environ else 'info' capture_output = True errorlog = f"./log/error_{date}.log" pythonpath = "., ./modules" diff --git a/src/modules/AmneziaWireguardConfiguration.py b/src/modules/AmneziaConfiguration.py similarity index 63% rename from src/modules/AmneziaWireguardConfiguration.py rename to src/modules/AmneziaConfiguration.py index 6ada7d5f..6ddba994 100644 --- a/src/modules/AmneziaWireguardConfiguration.py +++ b/src/modules/AmneziaConfiguration.py @@ -4,28 +4,39 @@ AmneziaWG Configuration import random, sqlalchemy, os, subprocess, re, uuid from flask import current_app from .PeerJobs import PeerJobs -from .AmneziaWGPeer import AmneziaWGPeer +from .AmneziaPeer import AmneziaPeer from .PeerShareLinks import PeerShareLinks -from .Utilities import RegexMatch +from .Utilities import RegexMatch, CheckAddress, CheckPeerKey from .WireguardConfiguration import WireguardConfiguration from .DashboardWebHooks import DashboardWebHooks -class AmneziaWireguardConfiguration(WireguardConfiguration): - def __init__(self, DashboardConfig, +class AmneziaConfiguration(WireguardConfiguration): + def __init__(self, + DashboardConfig, AllPeerJobs: PeerJobs, AllPeerShareLinks: PeerShareLinks, DashboardWebHooks: DashboardWebHooks, - name: str = None, data: dict = None, backup: dict = None, startup: bool = False): + name: str = None, + data: dict = None, + backup: dict = None, + startup: bool = False): self.Jc = 0 self.Jmin = 0 self.Jmax = 0 self.S1 = 0 self.S2 = 0 + self.S3 = 0 + self.S4 = 0 self.H1 = 1 self.H2 = 2 self.H3 = 3 self.H4 = 4 + self.I1 = "0" + self.I2 = "0" + self.I3 = "0" + self.I4 = "0" + self.I5 = "0" super().__init__(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, name, data, backup, startup, wg=False) @@ -58,65 +69,64 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): "Jmax": self.Jmax, "S1": self.S1, "S2": self.S2, + "S3": self.S3, + "S4": self.S4, "H1": self.H1, "H2": self.H2, "H3": self.H3, - "H4": self.H4 + "H4": self.H4, + "I1": self.I1, + "I2": self.I2, + "I3": self.I3, + "I4": self.I4, + "I5": self.I5 } def createDatabase(self, dbName = None): + def generate_column_obj(): + return [ + sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), + sqlalchemy.Column('private_key', sqlalchemy.String(255)), + sqlalchemy.Column('DNS', sqlalchemy.Text), + sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), + sqlalchemy.Column('name', sqlalchemy.Text), + sqlalchemy.Column('total_receive', sqlalchemy.Float), + sqlalchemy.Column('total_sent', sqlalchemy.Float), + sqlalchemy.Column('total_data', sqlalchemy.Float), + sqlalchemy.Column('endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('status', sqlalchemy.String(255)), + sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), + sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), + sqlalchemy.Column('cumu_receive', sqlalchemy.Float), + sqlalchemy.Column('cumu_sent', sqlalchemy.Float), + sqlalchemy.Column('cumu_data', sqlalchemy.Float), + sqlalchemy.Column('mtu', sqlalchemy.Integer), + sqlalchemy.Column('keepalive', sqlalchemy.Integer), + sqlalchemy.Column('notes', sqlalchemy.Text), + sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('preshared_key', sqlalchemy.String(255)) + ] + if dbName is None: dbName = self.Name - self.peersTable = sqlalchemy.Table( - dbName, self.metadata, - sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), - sqlalchemy.Column('private_key', sqlalchemy.String(255)), - sqlalchemy.Column('DNS', sqlalchemy.Text), - sqlalchemy.Column('advanced_security', sqlalchemy.String(255)), - sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), - sqlalchemy.Column('name', sqlalchemy.Text), - sqlalchemy.Column('total_receive', sqlalchemy.Float), - sqlalchemy.Column('total_sent', sqlalchemy.Float), - sqlalchemy.Column('total_data', sqlalchemy.Float), - sqlalchemy.Column('endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), - sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), - sqlalchemy.Column('cumu_receive', sqlalchemy.Float), - sqlalchemy.Column('cumu_sent', sqlalchemy.Float), - sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('mtu', sqlalchemy.Integer), - sqlalchemy.Column('keepalive', sqlalchemy.Integer), - sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), - extend_existing=True + f'{dbName}', self.metadata, *generate_column_obj(), extend_existing=True ) + self.peersRestrictedTable = sqlalchemy.Table( - f'{dbName}_restrict_access', self.metadata, - sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), - sqlalchemy.Column('private_key', sqlalchemy.String(255)), - sqlalchemy.Column('DNS', sqlalchemy.Text), - sqlalchemy.Column('advanced_security', sqlalchemy.String(255)), - sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), - sqlalchemy.Column('name', sqlalchemy.Text), - sqlalchemy.Column('total_receive', sqlalchemy.Float), - sqlalchemy.Column('total_sent', sqlalchemy.Float), - sqlalchemy.Column('total_data', sqlalchemy.Float), - sqlalchemy.Column('endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), - sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), - sqlalchemy.Column('cumu_receive', sqlalchemy.Float), - sqlalchemy.Column('cumu_sent', sqlalchemy.Float), - sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('mtu', sqlalchemy.Integer), - sqlalchemy.Column('keepalive', sqlalchemy.Integer), - sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), - extend_existing=True + f'{dbName}_restrict_access', self.metadata, *generate_column_obj(), extend_existing=True ) + + self.peersDeletedTable = sqlalchemy.Table( + f'{dbName}_deleted', self.metadata, *generate_column_obj(), extend_existing=True + ) + + if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite': + time_col_type = sqlalchemy.DATETIME + else: + time_col_type = sqlalchemy.TIMESTAMP + self.peersTransferTable = sqlalchemy.Table( f'{dbName}_transfer', self.metadata, sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), @@ -126,38 +136,7 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): sqlalchemy.Column('cumu_receive', sqlalchemy.Float), sqlalchemy.Column('cumu_sent', sqlalchemy.Float), sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('time', (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP), - server_default=sqlalchemy.func.now()), - extend_existing=True - ) - self.peersDeletedTable = sqlalchemy.Table( - f'{dbName}_deleted', self.metadata, - sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), - sqlalchemy.Column('private_key', sqlalchemy.String(255)), - sqlalchemy.Column('DNS', sqlalchemy.Text), - sqlalchemy.Column('advanced_security', sqlalchemy.String(255)), - sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), - sqlalchemy.Column('name', sqlalchemy.Text), - sqlalchemy.Column('total_receive', sqlalchemy.Float), - sqlalchemy.Column('total_sent', sqlalchemy.Float), - sqlalchemy.Column('total_data', sqlalchemy.Float), - sqlalchemy.Column('endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), - sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), - sqlalchemy.Column('cumu_receive', sqlalchemy.Float), - sqlalchemy.Column('cumu_sent', sqlalchemy.Float), - sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('mtu', sqlalchemy.Integer), - sqlalchemy.Column('keepalive', sqlalchemy.Integer), - sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), - extend_existing=True - ) - self.infoTable = sqlalchemy.Table( - 'ConfigurationsInfo', self.metadata, - sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True), - sqlalchemy.Column('Info', sqlalchemy.Text), + sqlalchemy.Column('time', time_col_type, server_default=sqlalchemy.func.now()), extend_existing=True ) @@ -165,15 +144,20 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): f'{dbName}_history_endpoint', self.metadata, sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False), - sqlalchemy.Column('time', - (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP)), + sqlalchemy.Column('time', time_col_type) + ) + + self.infoTable = sqlalchemy.Table( + 'ConfigurationsInfo', self.metadata, + sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True), + sqlalchemy.Column('Info', sqlalchemy.Text), extend_existing=True ) self.metadata.create_all(self.engine) def getPeers(self): - self.Peers.clear() + self.Peers.clear() if self.configurationFileChanged(): with open(self.configPath, 'r') as configFile: p = [] @@ -211,11 +195,9 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): if tempPeer is None: tempPeer = { "id": i['PublicKey'], - "advanced_security": i.get('AdvancedSecurity', 'off'), "private_key": "", "DNS": self.DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1], - "endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[ - 1], + "endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1], "name": i.get("name"), "total_receive": 0, "total_sent": 0, @@ -229,6 +211,7 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): "cumu_data": 0, "mtu": self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1], "keepalive": self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1], + "notes": "", "remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], "preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else "" } @@ -243,14 +226,14 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): self.peersTable.columns.id == i['PublicKey'] ) ) - self.Peers.append(AmneziaWGPeer(tempPeer, self)) + self.Peers.append(AmneziaPeer(tempPeer, self)) except Exception as e: current_app.logger.error(f"{self.Name} getPeers() Error", e) else: with self.engine.connect() as conn: existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall() for i in existingPeers: - self.Peers.append(AmneziaWGPeer(i, self)) + self.Peers.append(AmneziaPeer(i, self)) def addPeers(self, peers: list) -> tuple[bool, list, str]: result = { @@ -258,6 +241,15 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): "peers": [] } try: + cleanedAllowedIPs = {} + for p in peers: + newAllowedIPs = p['allowed_ip'].replace(" ", "") + if not CheckAddress(newAllowedIPs): + return False, [], "Allowed IPs entry format is incorrect" + if not CheckPeerKey(p["id"]): + return False, [], "Peer key format is incorrect" + cleanedAllowedIPs[p["id"]] = newAllowedIPs + with self.engine.begin() as conn: for i in peers: newPeer = { @@ -278,9 +270,9 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): "cumu_data": 0, "mtu": i['mtu'], "keepalive": i['keepalive'], + "notes": i.get('notes', ''), "remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], - "preshared_key": i["preshared_key"], - "advanced_security": i['advanced_security'] + "preshared_key": i["preshared_key"] } conn.execute( self.peersTable.insert().values(newPeer) @@ -293,13 +285,15 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): with open(uid, "w+") as f: f.write(p['preshared_key']) - subprocess.check_output( - f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", - shell=True, stderr=subprocess.STDOUT) + command = [self.Protocol, "set", self.Name, "peer", p['id'], "allowed-ips", cleanedAllowedIPs[p["id"]], "preshared-key", uid if presharedKeyExist else "/dev/null"] + subprocess.check_output(command, stderr=subprocess.STDOUT) + if presharedKeyExist: os.remove(uid) - subprocess.check_output( - f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) + + command = [f"{self.Protocol}-quick", "save", self.Name] + subprocess.check_output(command, stderr=subprocess.STDOUT) + self.getPeers() for p in peers: p = self.searchPeer(p['id']) @@ -311,7 +305,7 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): }) except Exception as e: current_app.logger.error("Add peers error", e) - return False, [], str(e) + return False, [], "Internal server error" return True, result['peers'], "" def getRestrictedPeers(self): @@ -319,4 +313,4 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): with self.engine.connect() as conn: restricted = conn.execute(self.peersRestrictedTable.select()).mappings().fetchall() for i in restricted: - self.RestrictedPeers.append(AmneziaWGPeer(i, self)) \ No newline at end of file + self.RestrictedPeers.append(AmneziaPeer(i, self)) diff --git a/src/modules/AmneziaPeer.py b/src/modules/AmneziaPeer.py new file mode 100644 index 00000000..509f4305 --- /dev/null +++ b/src/modules/AmneziaPeer.py @@ -0,0 +1,120 @@ +import os +from flask import current_app +import random +import re +import subprocess +import uuid + +from flask import current_app +from .Peer import Peer +from .Utilities import CheckAddress, ValidateDNSAddress, GenerateWireguardPublicKey + + +class AmneziaPeer(Peer): + def __init__(self, tableData, configuration): + super().__init__(tableData, configuration) + + + def updatePeer(self, name: str, private_key: str, + preshared_key: str, + dns_addresses: str, + allowed_ip: str, + endpoint_allowed_ip: str, + mtu: int, + keepalive: int, + notes: str + ) -> tuple[bool, str | None]: + + if not self.configuration.getStatus(): + self.configuration.toggleConfiguration() + + # Before we do any compute, let us check if the given endpoint allowed ip is valid at all + if not CheckAddress(endpoint_allowed_ip): + return False, f"Endpoint Allowed IPs format is incorrect" + + peers = [] + for peer in self.configuration.getPeersList(): + # Make sure to exclude your own data when updating since its not really relevant + if peer.id != self.id: + continue + peers.append(peer) + + used_allowed_ips = [] + for peer in peers: + ips = peer.allowed_ip.split(',') + ips = [ip.strip() for ip in ips] + used_allowed_ips.append(ips) + + if allowed_ip in used_allowed_ips: + return False, "Allowed IP already taken by another peer" + + if not ValidateDNSAddress(dns_addresses): + return False, f"DNS IP-Address or FQDN is incorrect" + + if isinstance(mtu, str): + mtu = 0 + + if isinstance(keepalive, str): + keepalive = 0 + + if mtu not in range(0, 1461): + return False, "MTU format is not correct" + + if keepalive < 0: + return False, "Persistent Keepalive format is not correct" + + if len(private_key) > 0: + pubKey = GenerateWireguardPublicKey(private_key) + if not pubKey[0] or pubKey[1] != self.id: + return False, "Private key does not match with the public key" + + try: + rand = random.Random() + uid = str(uuid.UUID(int=rand.getrandbits(128), version=4)) + psk_exist = len(preshared_key) > 0 + + if psk_exist: + with open(uid, "w+") as f: + f.write(preshared_key) + + newAllowedIPs = allowed_ip.replace(" ", "") + if not CheckAddress(newAllowedIPs): + return False, "Allowed IPs entry format is incorrect" + + command = [self.configuration.Protocol, "set", self.configuration.Name, "peer", self.id, "allowed-ips", newAllowedIPs, "preshared-key", uid if psk_exist else "/dev/null"] + + updateAllowedIp = subprocess.check_output(command, stderr=subprocess.STDOUT) + + if psk_exist: os.remove(uid) + + if len(updateAllowedIp.decode().strip("\n")) != 0: + current_app.logger.error(f"Update peer failed when updating Allowed IPs.\nInput: {newAllowedIPs}\nOutput: {updateAllowedIp.decode().strip('\n')}") + return False, "Internal server error" + + command = [f"{self.configuration.Protocol}-quick", "save", self.configuration.Name] + saveConfig = subprocess.check_output(command, stderr=subprocess.STDOUT) + + if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): + current_app.logger.error("Update peer failed when saving the configuration") + return False, "Internal server error" + + with self.configuration.engine.begin() as conn: + conn.execute( + self.configuration.peersTable.update().values({ + "name": name, + "private_key": private_key, + "DNS": dns_addresses, + "endpoint_allowed_ip": endpoint_allowed_ip, + "mtu": mtu, + "keepalive": keepalive, + "notes": notes, + "preshared_key": preshared_key + }).where( + self.configuration.peersTable.c.id == self.id + ) + ) + self.configuration.getPeers() + return True, None + except subprocess.CalledProcessError as exc: + current_app.logger.error(f"Subprocess call failed:\n{exc.output.decode("UTF-8")}") + return False, "Internal server error" diff --git a/src/modules/AmneziaWGPeer.py b/src/modules/AmneziaWGPeer.py deleted file mode 100644 index 17101b6b..00000000 --- a/src/modules/AmneziaWGPeer.py +++ /dev/null @@ -1,92 +0,0 @@ -import os -import random -import re -import subprocess -import uuid - -from .Peer import Peer -from .Utilities import ValidateIPAddressesWithRange, ValidateDNSAddress, GenerateWireguardPublicKey - - -class AmneziaWGPeer(Peer): - def __init__(self, tableData, configuration): - self.advanced_security = tableData["advanced_security"] - super().__init__(tableData, configuration) - - - def updatePeer(self, name: str, private_key: str, - preshared_key: str, - dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int, - keepalive: int, advanced_security: str) -> tuple[bool, str] or tuple[bool, None]: - if not self.configuration.getStatus(): - self.configuration.toggleConfiguration() - - existingAllowedIps = [item for row in list( - map(lambda x: [q.strip() for q in x.split(',')], - map(lambda y: y.allowed_ip, - list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row] - - if allowed_ip in existingAllowedIps: - return False, "Allowed IP already taken by another peer" - if not ValidateIPAddressesWithRange(endpoint_allowed_ip): - return False, f"Endpoint Allowed IPs format is incorrect" - if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses): - return False, f"DNS format is incorrect" - - if type(mtu) is str: - mtu = 0 - - if type(keepalive) is str: - keepalive = 0 - - if mtu < 0 or mtu > 1460: - return False, "MTU format is not correct" - if keepalive < 0: - return False, "Persistent Keepalive format is not correct" - if advanced_security != "on" and advanced_security != "off": - return False, "Advanced Security can only be on or off" - if len(private_key) > 0: - pubKey = GenerateWireguardPublicKey(private_key) - if not pubKey[0] or pubKey[1] != self.id: - return False, "Private key does not match with the public key" - try: - rd = random.Random() - uid = str(uuid.UUID(int=rd.getrandbits(128), version=4)) - pskExist = len(preshared_key) > 0 - - if pskExist: - with open(uid, "w+") as f: - f.write(preshared_key) - newAllowedIPs = allowed_ip.replace(" ", "") - updateAllowedIp = subprocess.check_output( - f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}", - shell=True, stderr=subprocess.STDOUT) - - if pskExist: os.remove(uid) - - if len(updateAllowedIp.decode().strip("\n")) != 0: - return False, "Update peer failed when updating Allowed IPs" - saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}", - shell=True, stderr=subprocess.STDOUT) - if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): - return False, "Update peer failed when saving the configuration" - - with self.configuration.engine.begin() as conn: - conn.execute( - self.configuration.peersTable.update().values({ - "name": name, - "private_key": private_key, - "DNS": dns_addresses, - "endpoint_allowed_ip": endpoint_allowed_ip, - "mtu": mtu, - "keepalive": keepalive, - "preshared_key": preshared_key, - "advanced_security": advanced_security - }).where( - self.configuration.peersTable.c.id == self.id - ) - ) - self.configuration.getPeers() - return True, None - except subprocess.CalledProcessError as exc: - return False, exc.output.decode("UTF-8").strip() \ No newline at end of file diff --git a/src/modules/DashboardClients.py b/src/modules/DashboardClients.py index 231141b1..54ba4802 100644 --- a/src/modules/DashboardClients.py +++ b/src/modules/DashboardClients.py @@ -8,7 +8,7 @@ import pyotp import sqlalchemy as db import requests -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString from .DashboardClientsPeerAssignment import DashboardClientsPeerAssignment from .DashboardClientsTOTP import DashboardClientsTOTP from .DashboardOIDC import DashboardOIDC diff --git a/src/modules/DashboardClientsPeerAssignment.py b/src/modules/DashboardClientsPeerAssignment.py index 80722d06..c507e5aa 100644 --- a/src/modules/DashboardClientsPeerAssignment.py +++ b/src/modules/DashboardClientsPeerAssignment.py @@ -1,7 +1,7 @@ import datetime import uuid -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString from .DashboardLogger import DashboardLogger import sqlalchemy as db from .WireguardConfiguration import WireguardConfiguration diff --git a/src/modules/DashboardClientsTOTP.py b/src/modules/DashboardClientsTOTP.py index e3830fb5..e3e1f5f6 100644 --- a/src/modules/DashboardClientsTOTP.py +++ b/src/modules/DashboardClientsTOTP.py @@ -3,7 +3,7 @@ import hashlib import uuid import sqlalchemy as db -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString class DashboardClientsTOTP: diff --git a/src/modules/DashboardConfig.py b/src/modules/DashboardConfig.py index bcdd8ff4..b1a63499 100644 --- a/src/modules/DashboardConfig.py +++ b/src/modules/DashboardConfig.py @@ -7,19 +7,15 @@ import sqlalchemy as db from datetime import datetime from typing import Any from flask import current_app -from .ConnectionString import ConnectionString -from .Utilities import ( - GetRemoteEndpoint, ValidateDNSAddress -) +from .DatabaseConnection import ConnectionString +from .Utilities import (GetRemoteEndpoint, ValidateDNSAddress) from .DashboardAPIKey import DashboardAPIKey - - class DashboardConfig: - DashboardVersion = 'v4.3.2' + DashboardVersion = 'v4.3.3' ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.') ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard.ini') - + def __init__(self): if not os.path.exists(DashboardConfig.ConfigurationFilePath): open(DashboardConfig.ConfigurationFilePath, "x") @@ -84,9 +80,11 @@ class DashboardConfig: }, "Clients": { "enable": "true", + "sign_up": "true" }, "WireGuardConfiguration": { - "autostart": "" + "autostart": "", + "peer_tracking": "false" } } @@ -103,12 +101,63 @@ class DashboardConfig: self.APIAccessed = False self.SetConfig("Server", "version", DashboardConfig.DashboardVersion) + def EnsureDatabaseIntegrity(self, wireguardConfigurations): + expected_columns = { + 'id': db.String(255), + 'private_key': db.String(255), + 'DNS': db.Text, + 'endpoint_allowed_ip': db.Text, + 'name': db.Text, + 'total_receive': db.Float, + 'total_sent': db.Float, + 'total_data': db.Float, + 'endpoint': db.String(255), + 'status': db.String(255), + 'latest_handshake': db.String(255), + 'allowed_ip': db.String(255), + 'cumu_receive': db.Float, + 'cumu_sent': db.Float, + 'cumu_data': db.Float, + 'mtu': db.Integer, + 'keepalive': db.Integer, + 'notes': db.Text, + 'remote_endpoint': db.String(255), + 'preshared_key': db.String(255) + } + + inspector = db.inspect(self.engine) + + with self.engine.begin() as conn: + for cfg_name, cfg_obj in wireguardConfigurations.items(): + tables_to_check = [ + cfg_name, + f'{cfg_name}_restrict_access', + f'{cfg_name}_deleted' + ] + + for table_name in tables_to_check: + if not table_name: + continue + if not inspector.has_table(table_name): + continue + + existing_columns = [c['name'] for c in inspector.get_columns(table_name)] + + for col_name, col_type in expected_columns.items(): + if col_name not in existing_columns: + type_str = col_type().compile(dialect=self.engine.dialect) + current_app.logger.info(f"Adding missing column '{col_name}' to table '{table_name}'") + preparer = self.engine.dialect.identifier_preparer + quoted_table = preparer.quote_identifier(table_name) + quoted_column = preparer.quote_identifier(col_name) + conn.execute(db.text(f"ALTER TABLE {quoted_table} ADD COLUMN {quoted_column} {type_str}")) + def getConnectionString(self, database) -> str or None: sqlitePath = os.path.join(DashboardConfig.ConfigurationPath, "db") - + if not os.path.isdir(sqlitePath): os.mkdir(sqlitePath) - + if self.GetConfig("Database", "type")[1] == "postgresql": cn = f'postgresql+psycopg2://{self.GetConfig("Database", "username")[1]}:{self.GetConfig("Database", "password")[1]}@{self.GetConfig("Database", "host")[1]}/{database}' elif self.GetConfig("Database", "type")[1] == "mysql": @@ -117,7 +166,7 @@ class DashboardConfig: cn = f'sqlite:///{os.path.join(sqlitePath, f"{database}.db")}' if not database_exists(cn): create_database(cn) - return cn + return cn def __createAPIKeyTable(self): self.apiKeyTable = db.Table('DashboardAPIKeys', self.dbMetadata, diff --git a/src/modules/DashboardLogger.py b/src/modules/DashboardLogger.py index 9b4e1f24..5d2b8ced 100644 --- a/src/modules/DashboardLogger.py +++ b/src/modules/DashboardLogger.py @@ -4,7 +4,7 @@ Dashboard Logger Class import uuid import sqlalchemy as db from flask import current_app -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString class DashboardLogger: diff --git a/src/modules/DashboardWebHooks.py b/src/modules/DashboardWebHooks.py index a598444b..ebaf43cd 100644 --- a/src/modules/DashboardWebHooks.py +++ b/src/modules/DashboardWebHooks.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta import requests from pydantic import BaseModel, field_serializer import sqlalchemy as db -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString from flask import current_app WebHookActions = ['peer_created', 'peer_deleted', 'peer_updated'] diff --git a/src/modules/ConnectionString.py b/src/modules/DatabaseConnection.py similarity index 88% rename from src/modules/ConnectionString.py rename to src/modules/DatabaseConnection.py index 77f69644..e3d7d1a0 100644 --- a/src/modules/ConnectionString.py +++ b/src/modules/DatabaseConnection.py @@ -1,14 +1,15 @@ import configparser import os from sqlalchemy_utils import database_exists, create_database -from flask import current_app def ConnectionString(database) -> str: parser = configparser.ConfigParser(strict=False) parser.read_file(open('wg-dashboard.ini', "r+")) + sqlitePath = os.path.join("db") if not os.path.isdir(sqlitePath): os.mkdir(sqlitePath) + if parser.get("Database", "type") == "postgresql": cn = f'postgresql+psycopg://{parser.get("Database", "username")}:{parser.get("Database", "password")}@{parser.get("Database", "host")}/{database}' elif parser.get("Database", "type") == "mysql": @@ -19,7 +20,6 @@ def ConnectionString(database) -> str: if not database_exists(cn): create_database(cn) except Exception as e: - current_app.logger.error("Database error. Terminating...", e) exit(1) - + return cn \ No newline at end of file diff --git a/src/modules/Email.py b/src/modules/Email.py index 145a4d72..d607973c 100644 --- a/src/modules/Email.py +++ b/src/modules/Email.py @@ -1,76 +1,101 @@ import os.path +import ssl import smtplib + +# Email libaries from email import encoders -from email.header import Header from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from email.utils import formataddr +from email.utils import formatdate class EmailSender: def __init__(self, DashboardConfig): - self.smtp = None self.DashboardConfig = DashboardConfig + if not os.path.exists('./attachments'): os.mkdir('./attachments') - - def Server(self): - return self.DashboardConfig.GetConfig("Email", "server")[1] - - def Port(self): - return self.DashboardConfig.GetConfig("Email", "port")[1] - - def Encryption(self): - return self.DashboardConfig.GetConfig("Email", "encryption")[1] - - def Username(self): - return self.DashboardConfig.GetConfig("Email", "username")[1] - - def Password(self): - return self.DashboardConfig.GetConfig("Email", "email_password")[1] - - def SendFrom(self): - return self.DashboardConfig.GetConfig("Email", "send_from")[1] - - # Thank you, @gdeeble from GitHub - def AuthenticationRequired(self): - return self.DashboardConfig.GetConfig("Email", "authentication_required")[1] - def ready(self): - if self.AuthenticationRequired(): - return all([self.Server(), self.Port(), self.Encryption(), self.Username(), self.Password(), self.SendFrom()]) - return all([self.Server(), self.Port(), self.Encryption(), self.SendFrom()]) + self.refresh_vals() - def send(self, receiver, subject, body, includeAttachment = False, attachmentName = "") -> tuple[bool, str] | tuple[bool, None]: - if self.ready(): - try: - self.smtp = smtplib.SMTP(self.Server(), port=int(self.Port())) - self.smtp.ehlo() - if self.Encryption() == "STARTTLS": - self.smtp.starttls() - if self.AuthenticationRequired(): - self.smtp.login(self.Username(), self.Password()) - message = MIMEMultipart() - message['Subject'] = subject - message['From'] = self.SendFrom() - message["To"] = receiver - message.attach(MIMEText(body, "plain")) + def refresh_vals(self) -> None: + self.Server = self.DashboardConfig.GetConfig("Email", "server")[1] + self.Port = self.DashboardConfig.GetConfig("Email", "port")[1] - if includeAttachment and len(attachmentName) > 0: - attachmentPath = os.path.join('./attachments', attachmentName) - if os.path.exists(attachmentPath): - attachment = MIMEBase("application", "octet-stream") - with open(os.path.join('./attachments', attachmentName), 'rb') as f: - attachment.set_payload(f.read()) - encoders.encode_base64(attachment) - attachment.add_header("Content-Disposition", f"attachment; filename= {attachmentName}",) - message.attach(attachment) - else: - self.smtp.close() - return False, "Attachment does not exist" - self.smtp.sendmail(self.SendFrom(), receiver, message.as_string()) - self.smtp.close() - return True, None - except Exception as e: - return False, f"Send failed | Reason: {e}" - return False, "SMTP not configured" \ No newline at end of file + self.Encryption = self.DashboardConfig.GetConfig("Email", "encryption")[1] + self.AuthRequired = self.DashboardConfig.GetConfig("Email", "authentication_required")[1] + self.Username = self.DashboardConfig.GetConfig("Email", "username")[1] + self.Password = self.DashboardConfig.GetConfig("Email", "email_password")[1] + + self.SendFrom = self.DashboardConfig.GetConfig("Email", "send_from")[1] + + def is_ready(self) -> bool: + self.refresh_vals() + + if self.AuthRequired: + ready = all([ + self.Server, self.Port, self.Encryption, + self.Username, self.Password, self.SendFrom + ]) + else: + ready = all([ + self.Server, self.Port, self.Encryption, self.SendFrom + ]) + return ready + + def send(self, receiver, subject, body, includeAttachment: bool = False, attachmentName: str = "") -> tuple[bool, str | None]: + if not self.is_ready(): + return False, "SMTP not configured" + + message = MIMEMultipart() + message['Subject'] = subject + message['From'] = self.SendFrom + message["To"] = receiver + message["Date"] = formatdate(localtime=True) + message.attach(MIMEText(body, "plain")) + + if includeAttachment and len(attachmentName) > 0: + attachmentPath = os.path.join('./attachments', attachmentName) + + if not os.path.exists(attachmentPath): + return False, "Attachment does not exist" + + attachment = MIMEBase("application", "octet-stream") + with open(os.path.join('./attachments', attachmentName), 'rb') as f: + attachment.set_payload(f.read()) + + encoders.encode_base64(attachment) + attachment.add_header("Content-Disposition", f"attachment; filename= {attachmentName}",) + message.attach(attachment) + + smtp = None + try: + context = ssl.create_default_context() + if self.Encryption == "IMPLICITTLS": + smtp = smtplib.SMTP_SSL(self.Server, port=int(self.Port), context=context) + else: + smtp = smtplib.SMTP(self.Server, port=int(self.Port)) + smtp.ehlo() + + # Configure SMTP encryption type + if self.Encryption == "STARTTLS": + smtp.starttls(context=context) + smtp.ehlo() + + # Log into the SMTP server if required + if self.AuthRequired: + smtp.login(self.Username, self.Password) + + # Send the actual email from the SMTP object + smtp.sendmail(self.SendFrom, receiver, message.as_string()) + return True, None + + except Exception as e: + return False, f"Send failed | Reason: {e}" + + finally: + if smtp: + try: + smtp.quit() + except Exception: + pass diff --git a/src/modules/NewConfigurationTemplates.py b/src/modules/NewConfigurationTemplates.py index 9c4511a4..05acceaa 100644 --- a/src/modules/NewConfigurationTemplates.py +++ b/src/modules/NewConfigurationTemplates.py @@ -2,7 +2,7 @@ import uuid from pydantic import BaseModel, field_serializer import sqlalchemy as db -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString class NewConfigurationTemplate(BaseModel): diff --git a/src/modules/Peer.py b/src/modules/Peer.py index 9201a9f0..93ee3122 100644 --- a/src/modules/Peer.py +++ b/src/modules/Peer.py @@ -10,8 +10,9 @@ from datetime import timedelta import jinja2 import sqlalchemy as db from .PeerJob import PeerJob +from flask import current_app from .PeerShareLink import PeerShareLink -from .Utilities import GenerateWireguardPublicKey, ValidateIPAddressesWithRange, ValidateDNSAddress +from .Utilities import GenerateWireguardPublicKey, CheckAddress, ValidateDNSAddress class Peer: @@ -34,6 +35,7 @@ class Peer: self.cumu_data = tableData["cumu_data"] self.mtu = tableData["mtu"] self.keepalive = tableData["keepalive"] + self.notes = tableData.get("notes", "") self.remote_endpoint = tableData["remote_endpoint"] self.preshared_key = tableData["preshared_key"] self.jobs: list[PeerJob] = [] @@ -49,62 +51,89 @@ class Peer: def __repr__(self): return str(self.toJson()) - def updatePeer(self, name: str, private_key: str, + def updatePeer(self, name: str, + private_key: str, preshared_key: str, - dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int, - keepalive: int) -> tuple[bool, str] or tuple[bool, None]: + dns_addresses: str, + allowed_ip: str, + endpoint_allowed_ip: str, + mtu: int, + keepalive: int, + notes: str + ) -> tuple[bool, str | None]: + if not self.configuration.getStatus(): self.configuration.toggleConfiguration() - existingAllowedIps = [item for row in list( - map(lambda x: [q.strip() for q in x.split(',')], - map(lambda y: y.allowed_ip, - list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row] - - if allowed_ip in existingAllowedIps: - return False, "Allowed IP already taken by another peer" - - if not ValidateIPAddressesWithRange(endpoint_allowed_ip): + # Before we do any compute, let us check if the given endpoint allowed ip is valid at all + if not CheckAddress(endpoint_allowed_ip): return False, f"Endpoint Allowed IPs format is incorrect" - - if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses): - return False, f"DNS format is incorrect" - - if type(mtu) is str or mtu is None: + + peers = [] + for peer in self.configuration.getPeersList(): + # Make sure to exclude your own data when updating since its not really relevant + if peer.id == self.id: + continue + peers.append(peer) + + used_allowed_ips = [] + for peer in peers: + ips = peer.allowed_ip.split(',') + ips = [ip.strip() for ip in ips] + used_allowed_ips.append(ips) + + if allowed_ip in used_allowed_ips: + return False, "Allowed IP already taken by another peer" + + if not ValidateDNSAddress(dns_addresses): + return False, f"DNS IP-Address or FQDN is incorrect" + + if isinstance(mtu, str): mtu = 0 - - if mtu < 0 or mtu > 1460: - return False, "MTU format is not correct" - - if type(keepalive) is str or keepalive is None: + + if isinstance(keepalive, str): keepalive = 0 - + + if mtu not in range(0, 1461): + return False, "MTU format is not correct" + if keepalive < 0: return False, "Persistent Keepalive format is not correct" + if len(private_key) > 0: pubKey = GenerateWireguardPublicKey(private_key) if not pubKey[0] or pubKey[1] != self.id: return False, "Private key does not match with the public key" - try: - rd = random.Random() - uid = str(uuid.UUID(int=rd.getrandbits(128), version=4)) - pskExist = len(preshared_key) > 0 - if pskExist: + try: + rand = random.Random() + uid = str(uuid.UUID(int=rand.getrandbits(128), version=4)) + psk_exist = len(preshared_key) > 0 + + if psk_exist: with open(uid, "w+") as f: f.write(preshared_key) - newAllowedIPs = allowed_ip.replace(" ", "") - updateAllowedIp = subprocess.check_output( - f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}", - shell=True, stderr=subprocess.STDOUT) - if pskExist: os.remove(uid) + newAllowedIPs = allowed_ip.replace(" ", "") + if not CheckAddress(newAllowedIPs): + return False, "Allowed IPs entry format is incorrect" + + command = [self.configuration.Protocol, "set", self.configuration.Name, "peer", self.id, "allowed-ips", newAllowedIPs, "preshared-key", uid if psk_exist else "/dev/null"] + updateAllowedIp = subprocess.check_output(command, stderr=subprocess.STDOUT) + + if psk_exist: os.remove(uid) + if len(updateAllowedIp.decode().strip("\n")) != 0: - return False, "Update peer failed when updating Allowed IPs" - saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}", - shell=True, stderr=subprocess.STDOUT) + current_app.logger.error("Update peer failed when updating Allowed IPs") + return False, "Internal server error" + + command = [f"{self.configuration.Protocol}-quick", "save", self.configuration.Name] + saveConfig = subprocess.check_output(command, stderr=subprocess.STDOUT) + if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): - return False, "Update peer failed when saving the configuration" + current_app.logger.error("Update peer failed when saving the configuration") + return False, "Internal server error" + with self.configuration.engine.begin() as conn: conn.execute( self.configuration.peersTable.update().values({ @@ -114,6 +143,7 @@ class Peer: "endpoint_allowed_ip": endpoint_allowed_ip, "mtu": mtu, "keepalive": keepalive, + "notes": notes, "preshared_key": preshared_key }).where( self.configuration.peersTable.c.id == self.id @@ -121,7 +151,8 @@ class Peer: ) return True, None except subprocess.CalledProcessError as exc: - return False, exc.output.decode("UTF-8").strip() + current_app.logger.error(f"Subprocess call failed:\n{exc.output.decode("UTF-8")}") + return False, "Internal server error" def downloadPeer(self) -> dict[str, str]: final = { @@ -132,17 +163,19 @@ class Peer: if len(filename) == 0: filename = "UntitledPeer" filename = "".join(filename.split(' ')) - filename = f"{filename}" - illegal_filename = [".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2", "com3", - "com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", - "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"] - for i in illegal_filename: - filename = filename.replace(i, "") + + # use previous filtering code if code below is insufficient or faulty + filename = re.sub(r'[.,/?<>\\:*|"]', '', filename).rstrip(". ") # remove special characters + + reserved_pattern = r"^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)?$" # match com1-9, lpt1-9, con, nul, prn, aux, nul + + if re.match(reserved_pattern, filename, re.IGNORECASE): + filename = f"file_{filename}" # prepend "file_" if it matches for i in filename: if re.match("^[a-zA-Z0-9_=+.-]$", i): final["fileName"] += i - + interfaceSection = { "PrivateKey": self.private_key, "Address": self.allowed_ip, @@ -155,7 +188,7 @@ class Peer: if self.configuration.configurationInfo.OverridePeerSettings.DNS else self.DNS ) } - + if self.configuration.Protocol == "awg": interfaceSection.update({ "Jc": self.configuration.Jc, @@ -163,12 +196,19 @@ class Peer: "Jmax": self.configuration.Jmax, "S1": self.configuration.S1, "S2": self.configuration.S2, + "S3": self.configuration.S3, + "S4": self.configuration.S4, "H1": self.configuration.H1, "H2": self.configuration.H2, "H3": self.configuration.H3, - "H4": self.configuration.H4 + "H4": self.configuration.H4, + "I1": self.configuration.I1, + "I2": self.configuration.I2, + "I3": self.configuration.I3, + "I4": self.configuration.I4, + "I5": self.configuration.I5 }) - + peerSection = { "PublicKey": self.configuration.PublicKey, "AllowedIPs": ( @@ -192,7 +232,7 @@ class Peer: for (key, val) in combine[s]: if val is not None and ((type(val) is str and len(val) > 0) or (type(val) is int and val > 0)): final["file"] += f"{key} = {val}\n" - + final["file"] = jinja2.Template(final["file"]).render(configuration=self.configuration) @@ -351,4 +391,4 @@ class Peer: hours, remainder = divmod(delta.total_seconds(), 3600) minutes, seconds = divmod(remainder, 60) - return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}" \ No newline at end of file + return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}" diff --git a/src/modules/PeerJobLogger.py b/src/modules/PeerJobLogger.py index 9f121971..4e74b822 100644 --- a/src/modules/PeerJobLogger.py +++ b/src/modules/PeerJobLogger.py @@ -8,7 +8,7 @@ import sqlalchemy as db from flask import current_app from sqlalchemy import RowMapping -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString from .Log import Log class PeerJobLogger: diff --git a/src/modules/PeerJobs.py b/src/modules/PeerJobs.py index 274a4263..c24e47fc 100644 --- a/src/modules/PeerJobs.py +++ b/src/modules/PeerJobs.py @@ -3,7 +3,7 @@ Peer Jobs """ import sqlalchemy -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString from .PeerJob import PeerJob from .PeerJobLogger import PeerJobLogger import sqlalchemy as db diff --git a/src/modules/PeerShareLinks.py b/src/modules/PeerShareLinks.py index 206e2fd0..eec6e898 100644 --- a/src/modules/PeerShareLinks.py +++ b/src/modules/PeerShareLinks.py @@ -1,4 +1,4 @@ -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString from .PeerShareLink import PeerShareLink import sqlalchemy as db from datetime import datetime diff --git a/src/modules/Utilities.py b/src/modules/Utilities.py index 0ba24066..661d3500 100644 --- a/src/modules/Utilities.py +++ b/src/modules/Utilities.py @@ -1,6 +1,6 @@ import re, ipaddress import subprocess - +import sqlalchemy def RegexMatch(regex, text) -> bool: """ @@ -18,10 +18,18 @@ def GetRemoteEndpoint() -> str: @return: """ import socket - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: - s.connect(("1.1.1.1", 80)) # Connecting to a public IP + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("1.1.1.1", 80)) # Connecting to a public IP wgd_remote_endpoint = s.getsockname()[0] return str(wgd_remote_endpoint) + except (socket.error, OSError): + pass + try: + return socket.gethostbyname(socket.gethostname()) + except (socket.error, OSError): + pass + return "127.0.0.1" def StringToBoolean(value: str): @@ -33,31 +41,35 @@ def StringToBoolean(value: str): return (value.strip().replace(" ", "").lower() in ("yes", "true", "t", "1", 1)) -def ValidateIPAddressesWithRange(ips: str) -> bool: - s = ips.replace(" ", "").split(",") - for ip in s: +def CheckAddress(ips_str: str) -> bool: + if len(ips_str) == 0: + return False + + for ip in ips_str.split(','): + stripped_ip = ip.strip() try: - ipaddress.ip_network(ip) - except ValueError as e: + # Verify the IP-address, with the strict flag as false also allows for /32 and /128 + ipaddress.ip_network(stripped_ip, strict=False) + except ValueError: return False return True -def ValidateIPAddresses(ips) -> bool: - s = ips.replace(" ", "").split(",") - for ip in s: - try: - ipaddress.ip_address(ip) - except ValueError as e: - return False - return True +def CheckPeerKey(peer_key: str) -> bool: + return re.match(r"^[A-Za-z0-9+/]{43}=$", peer_key) + +def ValidateDNSAddress(addresses_str: str) -> tuple[bool, str | None]: + if len(addresses_str) == 0: + return False, "Got an empty list/string to check for valid DNS-addresses" + + addresses = addresses_str.split(',') + for address in addresses: + stripped_address = address.strip() + + if not CheckAddress(stripped_address) and not RegexMatch(r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", stripped_address): + return False, f"{stripped_address} does not appear to be a valid IP-address or FQDN" + + return True, None -def ValidateDNSAddress(addresses) -> tuple[bool, str]: - s = addresses.replace(" ", "").split(",") - for address in s: - if not ValidateIPAddresses(address) and not RegexMatch( - r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", address): - return False, f"{address} does not appear to be an valid DNS address" - return True, "" def ValidateEndpointAllowedIPs(IPs) -> tuple[bool, str] | tuple[bool, None]: ips = IPs.replace(" ", "").split(",") @@ -101,4 +113,4 @@ def ValidatePasswordStrength(password: str) -> tuple[bool, str] | tuple[bool, No if not re.search(r'[$&+,:;=?@#|\'<>.\-^*()%!~_-]', password): return False, "Password must contain at least 1 special character from $&+,:;=?@#|'<>.-^*()%!~_-" - return True, None \ No newline at end of file + return True, None diff --git a/src/modules/WireguardConfiguration.py b/src/modules/WireguardConfiguration.py index f1fdfe16..548dfcbe 100644 --- a/src/modules/WireguardConfiguration.py +++ b/src/modules/WireguardConfiguration.py @@ -10,13 +10,18 @@ from datetime import datetime, timedelta from itertools import islice from flask import current_app -from .ConnectionString import ConnectionString +from .DatabaseConnection import ConnectionString from .DashboardConfig import DashboardConfig from .Peer import Peer from .PeerJobs import PeerJobs from .PeerShareLinks import PeerShareLinks -from .Utilities import StringToBoolean, GenerateWireguardPublicKey, RegexMatch, ValidateDNSAddress, \ - ValidateEndpointAllowedIPs +from .Utilities import StringToBoolean, \ + GenerateWireguardPublicKey, \ + RegexMatch, \ + ValidateDNSAddress, \ + ValidateEndpointAllowedIPs, \ + CheckAddress, \ + CheckPeerKey from .WireguardConfigurationInfo import WireguardConfigurationInfo, PeerGroupsClass from .DashboardWebHooks import DashboardWebHooks @@ -61,13 +66,14 @@ class WireguardConfiguration: self.Protocol = "wg" if wg else "awg" self.AllPeerJobs = AllPeerJobs self.DashboardConfig = DashboardConfig + self.DashboardConfig.EnsureDatabaseIntegrity({self.Name: self}) self.AllPeerShareLinks = AllPeerShareLinks self.DashboardWebHooks = DashboardWebHooks self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') self.engine: sqlalchemy.Engine = sqlalchemy.create_engine(ConnectionString("wgdashboard")) self.metadata: sqlalchemy.MetaData = sqlalchemy.MetaData() self.dbType = self.DashboardConfig.GetConfig("Database", "type")[1] - + if name is not None: if data is not None and "Backup" in data.keys(): db = self.__importDatabase( @@ -109,10 +115,17 @@ class WireguardConfiguration: self.__parser["Interface"]["Jmax"] = self.Jmax self.__parser["Interface"]["S1"] = self.S1 self.__parser["Interface"]["S2"] = self.S2 + self.__parser["Interface"]["S3"] = self.S3 + self.__parser["Interface"]["S4"] = self.S4 self.__parser["Interface"]["H1"] = self.H1 self.__parser["Interface"]["H2"] = self.H2 self.__parser["Interface"]["H3"] = self.H3 self.__parser["Interface"]["H4"] = self.H4 + self.__parser["Interface"]["I1"] = self.I1 + self.__parser["Interface"]["I2"] = self.I2 + self.__parser["Interface"]["I3"] = self.I3 + self.__parser["Interface"]["I4"] = self.I4 + self.__parser["Interface"]["I5"] = self.I5 if "Backup" not in data.keys(): self.createDatabase() @@ -127,8 +140,11 @@ class WireguardConfiguration: current_app.logger.info(f"Initialized Configuration: {name}") self.__dumpDatabase() if self.getAutostartStatus() and not self.getStatus() and startup: - self.toggleConfiguration() - current_app.logger.info(f"Autostart Configuration: {name}") + status, ext = self.toggleConfiguration() + if not status: + current_app.logger.error(f"Failed to autostart configuration: {name}. Reason: {ext}") + else: + current_app.logger.info(f"Autostart Configuration: {name}") self.configurationInfo: WireguardConfigurationInfo | None = None configurationInfoJson = self.readConfigurationInfo() @@ -140,7 +156,6 @@ class WireguardConfiguration: if self.Status: self.addAutostart() - def __getProtocolPath(self) -> str: _, path = self.DashboardConfig.GetConfig("Server", "wg_conf_path") if self.Protocol == "wg" \ @@ -232,54 +247,50 @@ class WireguardConfiguration: return True def createDatabase(self, dbName = None): + def generate_column_obj(): + return [ + sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), + sqlalchemy.Column('private_key', sqlalchemy.String(255)), + sqlalchemy.Column('DNS', sqlalchemy.Text), + sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), + sqlalchemy.Column('name', sqlalchemy.Text), + sqlalchemy.Column('total_receive', sqlalchemy.Float), + sqlalchemy.Column('total_sent', sqlalchemy.Float), + sqlalchemy.Column('total_data', sqlalchemy.Float), + sqlalchemy.Column('endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('status', sqlalchemy.String(255)), + sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), + sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), + sqlalchemy.Column('cumu_receive', sqlalchemy.Float), + sqlalchemy.Column('cumu_sent', sqlalchemy.Float), + sqlalchemy.Column('cumu_data', sqlalchemy.Float), + sqlalchemy.Column('mtu', sqlalchemy.Integer), + sqlalchemy.Column('keepalive', sqlalchemy.Integer), + sqlalchemy.Column('notes', sqlalchemy.Text), + sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('preshared_key', sqlalchemy.String(255)) + ] + if dbName is None: dbName = self.Name + self.peersTable = sqlalchemy.Table( - dbName, self.metadata, - sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), - sqlalchemy.Column('private_key', sqlalchemy.String(255)), - sqlalchemy.Column('DNS', sqlalchemy.Text), - sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), - sqlalchemy.Column('name', sqlalchemy.Text), - sqlalchemy.Column('total_receive', sqlalchemy.Float), - sqlalchemy.Column('total_sent', sqlalchemy.Float), - sqlalchemy.Column('total_data', sqlalchemy.Float), - sqlalchemy.Column('endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), - sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), - sqlalchemy.Column('cumu_receive', sqlalchemy.Float), - sqlalchemy.Column('cumu_sent', sqlalchemy.Float), - sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('mtu', sqlalchemy.Integer), - sqlalchemy.Column('keepalive', sqlalchemy.Integer), - sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), - extend_existing=True + f'{dbName}', self.metadata, *generate_column_obj(), extend_existing=True ) + self.peersRestrictedTable = sqlalchemy.Table( - f'{dbName}_restrict_access', self.metadata, - sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), - sqlalchemy.Column('private_key', sqlalchemy.String(255)), - sqlalchemy.Column('DNS', sqlalchemy.Text), - sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), - sqlalchemy.Column('name', sqlalchemy.Text), - sqlalchemy.Column('total_receive', sqlalchemy.Float), - sqlalchemy.Column('total_sent', sqlalchemy.Float), - sqlalchemy.Column('total_data', sqlalchemy.Float), - sqlalchemy.Column('endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), - sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), - sqlalchemy.Column('cumu_receive', sqlalchemy.Float), - sqlalchemy.Column('cumu_sent', sqlalchemy.Float), - sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('mtu', sqlalchemy.Integer), - sqlalchemy.Column('keepalive', sqlalchemy.Integer), - sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), - extend_existing=True + f'{dbName}_restrict_access', self.metadata, *generate_column_obj(), extend_existing=True ) + + self.peersDeletedTable = sqlalchemy.Table( + f'{dbName}_deleted', self.metadata, *generate_column_obj(), extend_existing=True + ) + + if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite': + time_col_type = sqlalchemy.DATETIME + else: + time_col_type = sqlalchemy.TIMESTAMP + self.peersTransferTable = sqlalchemy.Table( f'{dbName}_transfer', self.metadata, sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), @@ -289,8 +300,7 @@ class WireguardConfiguration: sqlalchemy.Column('cumu_receive', sqlalchemy.Float), sqlalchemy.Column('cumu_sent', sqlalchemy.Float), sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('time', (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP), - server_default=sqlalchemy.func.now()), + sqlalchemy.Column('time', time_col_type, server_default=sqlalchemy.func.now()), extend_existing=True ) @@ -298,34 +308,9 @@ class WireguardConfiguration: f'{dbName}_history_endpoint', self.metadata, sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False), - sqlalchemy.Column('time', - (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP)), - extend_existing=True + sqlalchemy.Column('time', time_col_type) ) - self.peersDeletedTable = sqlalchemy.Table( - f'{dbName}_deleted', self.metadata, - sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), - sqlalchemy.Column('private_key', sqlalchemy.String(255)), - sqlalchemy.Column('DNS', sqlalchemy.Text), - sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), - sqlalchemy.Column('name', sqlalchemy.Text), - sqlalchemy.Column('total_receive', sqlalchemy.Float), - sqlalchemy.Column('total_sent', sqlalchemy.Float), - sqlalchemy.Column('total_data', sqlalchemy.Float), - sqlalchemy.Column('endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), - sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), - sqlalchemy.Column('cumu_receive', sqlalchemy.Float), - sqlalchemy.Column('cumu_sent', sqlalchemy.Float), - sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('mtu', sqlalchemy.Integer), - sqlalchemy.Column('keepalive', sqlalchemy.Integer), - sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), - extend_existing=True - ) self.infoTable = sqlalchemy.Table( 'ConfigurationsInfo', self.metadata, sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True), @@ -404,6 +389,7 @@ class WireguardConfiguration: try: if "[Peer]" not in content: current_app.logger.info(f"{self.Name} config has no [Peer] section") + self.Peers = [] return peerStarts = content.index("[Peer]") @@ -439,8 +425,7 @@ class WireguardConfiguration: "id": i['PublicKey'], "private_key": "", "DNS": self.DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1], - "endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[ - 1], + "endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1], "name": i.get("name"), "total_receive": 0, "total_sent": 0, @@ -454,6 +439,7 @@ class WireguardConfiguration: "cumu_data": 0, "mtu": self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1] if len(self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1]) > 0 else None, "keepalive": self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1] if len(self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1]) > 0 else None, + "notes": "", "remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], "preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else "" } @@ -526,6 +512,15 @@ class WireguardConfiguration: "peers": [] } try: + cleanedAllowedIPs = {} + for p in peers: + newAllowedIPs = p['allowed_ip'].replace(" ", "") + if not CheckAddress(newAllowedIPs): + return False, [], "Allowed IPs entry format is incorrect" + if not CheckPeerKey(p["id"]): + return False, [], "Peer key format is incorrect" + cleanedAllowedIPs[p["id"]] = newAllowedIPs + with self.engine.begin() as conn: for i in peers: newPeer = { @@ -546,6 +541,7 @@ class WireguardConfiguration: "cumu_data": 0, "mtu": i['mtu'], "keepalive": i['keepalive'], + "notes": i.get("notes", ""), "remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], "preshared_key": i["preshared_key"] } @@ -560,12 +556,15 @@ class WireguardConfiguration: with open(uid, "w+") as f: f.write(p['preshared_key']) - subprocess.check_output(f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", - shell=True, stderr=subprocess.STDOUT) + command = [self.Protocol, "set", self.Name, "peer", p['id'], "allowed-ips", cleanedAllowedIPs[p["id"]], "preshared-key", uid if presharedKeyExist else "/dev/null"] + subprocess.check_output(command, stderr=subprocess.STDOUT) + if presharedKeyExist: os.remove(uid) - subprocess.check_output( - f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) + + command = [f"{self.Protocol}-quick", "save", self.Name] + subprocess.check_output(command, stderr=subprocess.STDOUT) + self.getPeers() for p in peers: p = self.searchPeer(p['id']) @@ -577,7 +576,7 @@ class WireguardConfiguration: }) except Exception as e: current_app.logger.error("Add peers error", e) - return False, [], str(e) + return False, [], "Internal server error" return True, result['peers'], "" def searchPeer(self, publicKey): @@ -615,8 +614,16 @@ class WireguardConfiguration: with open(uid, "w+") as f: f.write(restrictedPeer['preshared_key']) - subprocess.check_output(f"{self.Protocol} set {self.Name} peer {restrictedPeer['id']} allowed-ips {restrictedPeer['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", - shell=True, stderr=subprocess.STDOUT) + newAllowedIPs = restrictedPeer['allowed_ip'].replace(" ", "") + if not CheckAddress(newAllowedIPs): + return False, "Allowed IPs entry format is incorrect" + + if not CheckPeerKey(restrictedPeer["id"]): + return False, "Peer key format is incorrect" + + command = [self.Protocol, "set", self.Name, "peer", restrictedPeer["id"], "allowed-ips", newAllowedIPs, "preshared-key", uid if presharedKeyExist else "/dev/null"] + subprocess.check_output(command, stderr=subprocess.STDOUT) + if presharedKeyExist: os.remove(uid) else: return False, "Failed to allow access of peer " + i @@ -636,8 +643,9 @@ class WireguardConfiguration: found, pf = self.searchPeer(p) if found: try: - subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove", - shell=True, stderr=subprocess.STDOUT) + command = [self.Protocol, "set", self.Name, "peer", pf.id, "remove"] + subprocess.check_output(command, stderr=subprocess.STDOUT) + conn.execute( self.peersRestrictedTable.insert().from_select( [c.name for c in self.peersTable.columns], @@ -665,9 +673,8 @@ class WireguardConfiguration: if not self.__wgSave(): return False, "Failed to save configuration through WireGuard" - + self.getRestrictedPeers() self.getPeers() - if numOfRestrictedPeers == len(listOfPublicKeys): return True, f"Restricted {numOfRestrictedPeers} peer(s)" return False, f"Restricted {numOfRestrictedPeers} peer(s) successfully. Failed to restrict {numOfFailedToRestrictPeers} peer(s)" @@ -719,17 +726,20 @@ class WireguardConfiguration: def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]: try: - subprocess.check_output(f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) + command = [f"{self.Protocol}-quick", "save", self.Name] + subprocess.check_output(command, stderr=subprocess.STDOUT) + return True, None except subprocess.CalledProcessError as e: - return False, str(e) + current_app.logger.error(f"Failed to process command:\n{str(e)}") + return False, "Internal server error" def getPeersLatestHandshake(self): if not self.getStatus(): self.toggleConfiguration() try: - latestHandshake = subprocess.check_output(f"{self.Protocol} show {self.Name} latest-handshakes", - shell=True, stderr=subprocess.STDOUT) + command = [self.Protocol, "show", self.Name, "latest-handshakes"] + latestHandshake = subprocess.check_output(command, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: return "stopped" latestHandshake = latestHandshake.decode("UTF-8").split() @@ -768,8 +778,9 @@ class WireguardConfiguration: if not self.getStatus(): self.toggleConfiguration() # try: - data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} transfer", - shell=True, stderr=subprocess.STDOUT) + command = [self.Protocol, "show", self.Name, "transfer"] + data_usage = subprocess.check_output(command, stderr=subprocess.STDOUT) + data_usage = data_usage.decode("UTF-8").split("\n") data_usage = [p.split("\t") for p in data_usage] @@ -783,15 +794,13 @@ class WireguardConfiguration: ) ).mappings().fetchone() if cur_i is not None: - # print(cur_i is None) total_sent = cur_i['total_sent'] - # print(cur_i is None) total_receive = cur_i['total_receive'] cur_total_sent = float(data_usage[i][2]) / (1024 ** 3) cur_total_receive = float(data_usage[i][1]) / (1024 ** 3) cumulative_receive = cur_i['cumu_receive'] + total_receive cumulative_sent = cur_i['cumu_sent'] + total_sent - if total_sent <= cur_total_sent and total_receive <= cur_total_receive: + if (total_sent * 0.999 ) <= cur_total_sent and (total_receive * 0.999) <= cur_total_receive: # An accuracy of 1K ppm is sufficient total_sent = cur_total_sent total_receive = cur_total_receive else: @@ -826,10 +835,11 @@ class WireguardConfiguration: if not self.getStatus(): self.toggleConfiguration() try: - data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} endpoints", - shell=True, stderr=subprocess.STDOUT) + command = [self.Protocol, "show", self.Name, "endpoints"] + data_usage = subprocess.check_output(command, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: return "stopped" + data_usage = data_usage.decode("UTF-8").split() count = 0 with self.engine.begin() as conn: @@ -847,14 +857,17 @@ class WireguardConfiguration: self.getStatus() if self.Status: try: - check = subprocess.check_output(f"{self.Protocol}-quick down {self.Name}", - shell=True, stderr=subprocess.STDOUT) + command = [f"{self.Protocol}-quick", "down", self.Name] + check = subprocess.check_output(command, stderr=subprocess.STDOUT) + self.removeAutostart() except subprocess.CalledProcessError as exc: return False, str(exc.output.strip().decode("utf-8")) else: try: - check = subprocess.check_output(f"{self.Protocol}-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT) + command = [f"{self.Protocol}-quick", "up", self.Name] + check = subprocess.check_output(command, stderr=subprocess.STDOUT) + self.addAutostart() except subprocess.CalledProcessError as exc: return False, str(exc.output.strip().decode("utf-8")) @@ -921,8 +934,8 @@ class WireguardConfiguration: files.sort(key=lambda x: x[1], reverse=True) for f, ct in files: - if RegexMatch(f"^({self.Name})_(.*)\\.(conf)$", f): - s = re.search(f"^({self.Name})_(.*)\\.(conf)$", f) + if RegexMatch(rf"^({self.Name})_(\d+)\\.(conf)$", f): + s = re.search(rf"^({self.Name})_(\d+)\\.(conf)$", f) date = s.group(2) d = { "filename": f, @@ -995,7 +1008,7 @@ class WireguardConfiguration: original = [l.rstrip("\n") for l in f.readlines()] allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort", "Table"] if self.Protocol == 'awg': - allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4"] + allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "S3", "S4", "H1", "H2", "H3", "H4", "I1", "I2", "I3", "I4", "I5"] start = original.index("[Interface]") try: end = original.index("[Peer]") @@ -1033,31 +1046,33 @@ class WireguardConfiguration: return True def renameConfiguration(self, newConfigurationName) -> tuple[bool, str]: + newConfigurationName = os.path.basename(newConfigurationName) + + if len(newConfigurationName) > 15 or not re.match(r'^[a-zA-Z0-9_=\+\.\-]{1,15}$', newConfigurationName): + return False, "Configuration name is either too long or contains an illegal character" + + newConfigurationName = newConfigurationName.replace("`", "") # double check + try: if self.getStatus(): self.toggleConfiguration() self.createDatabase(newConfigurationName) with self.engine.begin() as conn: - conn.execute( - sqlalchemy.text( - f'INSERT INTO "{newConfigurationName}" SELECT * FROM "{self.Name}"' + def doRenameStatement(suffix): + newConfig = f"{newConfigurationName}{suffix}" + oldConfig = f"{self.Name}{suffix}" + + conn.execute( + sqlalchemy.text( + f'INSERT INTO `{newConfig}` SELECT * FROM `{oldConfig}`' + ) ) - ) - conn.execute( - sqlalchemy.text( - f'INSERT INTO "{newConfigurationName}_restrict_access" SELECT * FROM "{self.Name}_restrict_access"' - ) - ) - conn.execute( - sqlalchemy.text( - f'INSERT INTO "{newConfigurationName}_deleted" SELECT * FROM "{self.Name}_deleted"' - ) - ) - conn.execute( - sqlalchemy.text( - f'INSERT INTO "{newConfigurationName}_transfer" SELECT * FROM "{self.Name}_transfer"' - ) - ) + + doRenameStatement("") + doRenameStatement("_restrict_access") + doRenameStatement("_deleted") + doRenameStatement("_transfer") + self.AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName) shutil.copy( self.configPath, @@ -1065,8 +1080,8 @@ class WireguardConfiguration: ) self.deleteConfiguration() except Exception as e: - traceback.print_stack() - return False, str(e) + current_app.logger.error(f"Failed to rename configuration.\nNew Configuration Name: {newConfigurationName}\nError: {str(e)}") + return False, "Internal server error" return True, None def getNumberOfAvailableIP(self): @@ -1226,7 +1241,6 @@ class WireguardConfiguration: def __validateOverridePeerSettings(self, key: str, value: str | int) -> tuple[bool, None] | tuple[bool, str]: status = True msg = None - print(value) if key == "DNS" and value: status, msg = ValidateDNSAddress(value) elif key == "EndpointAllowedIPs" and value: @@ -1291,4 +1305,4 @@ class WireguardConfiguration: conn.execute(sqlalchemy.text('VACUUM;')) except Exception as e: return False - return True \ No newline at end of file + return True diff --git a/src/requirements.txt b/src/requirements.txt index ba02d26c..8ce49a64 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -8,10 +8,10 @@ icmplib==3.0.4 gunicorn==25.0.3 requests==2.32.5 tcconfig==0.30.1 -sqlalchemy==2.0.46 +sqlalchemy==2.0.49 sqlalchemy_utils==0.42.1 -psycopg[binary]==3.3.2 +psycopg[binary]==3.3.3 PyMySQL==1.1.2 tzlocal==5.3.1 python-jose==3.5.0 -pydantic==2.12.5 +pydantic==2.13.0 diff --git a/src/static/app/index.html b/src/static/app/index.html index 0c731866..afa86e31 100644 --- a/src/static/app/index.html +++ b/src/static/app/index.html @@ -6,14 +6,21 @@ - - + + WGDashboard + +
- diff --git a/src/static/app/package-lock.json b/src/static/app/package-lock.json index 99a27659..0812c476 100644 --- a/src/static/app/package-lock.json +++ b/src/static/app/package-lock.json @@ -1,43 +1,43 @@ { "name": "app", - "version": "4.3.1", + "version": "4.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "app", - "version": "4.3.1", + "version": "4.3.3", "dependencies": { "@volar/language-server": "2.4.28", "@vue/language-server": "3.2.4", "@vuepic/vue-datepicker": "^12.1.0", - "@vueuse/core": "^14.2.0", + "@vueuse/core": "^14.2.1", "@vueuse/shared": "^14.1.0", "animate.css": "^4.1.1", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.3", - "cidr-tools": "^11.0.8", + "cidr-tools": "^11.3.3", "css-color-converter": "^2.0.0", "dayjs": "^1.11.19", "electron-builder": "^26.7.0", - "fuse.js": "^7.0.0", + "fuse.js": "^7.3.0", "i": "^0.3.7", "is-cidr": "^6.0.3", "npm": "^11.8.0", - "ol": "^10.7.0", + "ol": "^10.8.0", "pinia": "^3.0.4", "pinia-plugin-persistedstate": "^4.7.1", "qrcode": "^1.5.3", "qrcodejs": "^1.0.0", "simple-code-editor": "^2.0.9", "uuid": "^13.0.0", - "vue": "^3.5.28", + "vue": "^3.5.32", "vue-chartjs": "^5.3.3", - "vue-router": "^5.0.2" + "vue-router": "^5.0.4" }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.4", - "vite": "^7.3.1" + "vite": "^8.0.8" } }, "node_modules/@babel/generator": { @@ -75,9 +75,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -143,9 +143,9 @@ } }, "node_modules/@electron/asar/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -353,9 +353,9 @@ } }, "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -376,12 +376,12 @@ } }, "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -482,446 +482,38 @@ "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@floating-ui/core": { @@ -986,27 +578,6 @@ } } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1225,6 +796,25 @@ "node": ">=10" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@npmcli/agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", @@ -1259,10 +849,20 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@petamoriken/float16": { - "version": "3.9.2", - "resolved": "https://registry.npmmirror.com/@petamoriken/float16/-/float16-3.9.2.tgz", - "integrity": "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz", + "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", "license": "MIT" }, "node_modules/@pkgjs/parseargs": { @@ -1286,31 +886,10 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz", - "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz", - "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", "cpu": [ "arm64" ], @@ -1319,12 +898,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz", - "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", "cpu": [ "arm64" ], @@ -1333,12 +915,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz", - "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", "cpu": [ "x64" ], @@ -1347,26 +932,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz", - "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz", - "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", "cpu": [ "x64" ], @@ -1375,12 +949,15 @@ "optional": true, "os": [ "freebsd" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz", - "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", "cpu": [ "arm" ], @@ -1389,26 +966,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz", - "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz", - "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", "cpu": [ "arm64" ], @@ -1417,12 +983,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz", - "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", "cpu": [ "arm64" ], @@ -1431,26 +1000,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz", - "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz", - "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", "cpu": [ "ppc64" ], @@ -1459,40 +1017,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz", - "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==", - "cpu": [ - "riscv64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz", - "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz", - "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", "cpu": [ "s390x" ], @@ -1501,12 +1034,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz", - "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", "cpu": [ "x64" ], @@ -1515,12 +1051,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz", - "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", "cpu": [ "x64" ], @@ -1529,12 +1068,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz", - "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", "cpu": [ "arm64" ], @@ -1543,12 +1085,34 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz", - "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", "cpu": [ "arm64" ], @@ -1557,26 +1121,15 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz", - "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==", - "cpu": [ - "ia32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz", - "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", "cpu": [ "x64" ], @@ -1585,7 +1138,17 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "dev": true, + "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", @@ -1611,6 +1174,17 @@ "node": ">=10" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -1632,13 +1206,6 @@ "@types/ms": "*" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -1874,53 +1441,53 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz", - "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.32.tgz", + "integrity": "sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/shared": "3.5.28", + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.32", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", - "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.32.tgz", + "integrity": "sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.28", - "@vue/shared": "3.5.28" + "@vue/compiler-core": "3.5.32", + "@vue/shared": "3.5.32" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", - "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.32.tgz", + "integrity": "sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/compiler-core": "3.5.28", - "@vue/compiler-dom": "3.5.28", - "@vue/compiler-ssr": "3.5.28", - "@vue/shared": "3.5.28", + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.32", + "@vue/compiler-dom": "3.5.32", + "@vue/compiler-ssr": "3.5.32", + "@vue/shared": "3.5.32", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", - "postcss": "^8.5.6", + "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", - "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.32.tgz", + "integrity": "sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.28", - "@vue/shared": "3.5.28" + "@vue/compiler-dom": "3.5.32", + "@vue/shared": "3.5.32" } }, "node_modules/@vue/devtools-api": { @@ -2029,53 +1596,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz", - "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.32.tgz", + "integrity": "sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.28" + "@vue/shared": "3.5.32" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz", - "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.32.tgz", + "integrity": "sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.28", - "@vue/shared": "3.5.28" + "@vue/reactivity": "3.5.32", + "@vue/shared": "3.5.32" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", - "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.32.tgz", + "integrity": "sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.28", - "@vue/runtime-core": "3.5.28", - "@vue/shared": "3.5.28", + "@vue/reactivity": "3.5.32", + "@vue/runtime-core": "3.5.32", + "@vue/shared": "3.5.32", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz", - "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.32.tgz", + "integrity": "sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.28", - "@vue/shared": "3.5.28" + "@vue/compiler-ssr": "3.5.32", + "@vue/shared": "3.5.32" }, "peerDependencies": { - "vue": "3.5.28" + "vue": "3.5.32" } }, "node_modules/@vue/shared": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz", - "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.32.tgz", + "integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==", "license": "MIT" }, "node_modules/@vue/typescript-plugin": { @@ -2110,14 +1677,14 @@ } }, "node_modules/@vueuse/core": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.0.tgz", - "integrity": "sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz", + "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "14.2.0", - "@vueuse/shared": "14.2.0" + "@vueuse/metadata": "14.2.1", + "@vueuse/shared": "14.2.1" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -2127,18 +1694,18 @@ } }, "node_modules/@vueuse/metadata": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.0.tgz", - "integrity": "sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz", + "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.0.tgz", - "integrity": "sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.1.tgz", + "integrity": "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -2156,6 +1723,16 @@ "node": ">=10.0.0" } }, + "node_modules/@zarrita/storage": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@zarrita/storage/-/storage-0.1.4.tgz", + "integrity": "sha512-qURfJAQcQGRfDQ4J9HaCjGaj3jlJKc66bnRk6G/IeLUsM7WKyG7Bzsuf1EZurSXyc0I4LVcu6HaeQQ4d3kZ16g==", + "license": "MIT", + "dependencies": { + "reference-spec-reader": "^0.2.0", + "unzipit": "1.4.3" + } + }, "node_modules/7zip-bin": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", @@ -2193,9 +1770,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -2515,9 +2092,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2615,9 +2192,9 @@ } }, "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2650,12 +2227,12 @@ "license": "ISC" }, "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2825,12 +2402,12 @@ } }, "node_modules/cidr-tools": { - "version": "11.0.8", - "resolved": "https://registry.npmjs.org/cidr-tools/-/cidr-tools-11.0.8.tgz", - "integrity": "sha512-z8TVpm6JAemTAuYsza3GL4KJH5VxSFwWge6YhA+9tFYoUICx8fraMdd6bCBb6Y/15o94POW32XghQt9LH4CHXQ==", + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/cidr-tools/-/cidr-tools-11.3.3.tgz", + "integrity": "sha512-3fHQpk8DxUjO/nuoo9gTDpOKQDHoHarCxU3b7bkAq/nMHm54ADqoSQRF3l/GVbnOEtt5wfo/3vTEp4imLb7BZQ==", "license": "BSD-2-Clause", "dependencies": { - "ip-bigint": "^8.2.4" + "ip-bigint": "^8.3.4" }, "engines": { "node": ">=18" @@ -3237,9 +2814,9 @@ } }, "node_modules/dir-compare/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3587,48 +3164,6 @@ "license": "MIT", "optional": true }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3708,6 +3243,12 @@ } } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -3718,18 +3259,18 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -3829,7 +3370,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, @@ -3852,28 +3393,32 @@ } }, "node_modules/fuse.js": { - "version": "7.1.0", - "resolved": "https://registry.npmmirror.com/fuse.js/-/fuse.js-7.1.0.tgz", - "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.3.0.tgz", + "integrity": "sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==", "license": "Apache-2.0", "engines": { "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/krisk" } }, "node_modules/geotiff": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/geotiff/-/geotiff-2.1.3.tgz", - "integrity": "sha512-PT6uoF5a1+kbC3tHmZSUsLHBp2QJlHasxxxxPW47QIY1VBKpFB+FcDvX+MxER6UzgLQZ0xDzJ9s48B9JbOCTqA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-3.0.5.tgz", + "integrity": "sha512-OWcL9S9+yDZ6iAlXMt32T1iwUApJM8UiD47xbm6ZP1h33d10fqkPs14EG/ttT5EnefpZSx3G15iDFC5FxUNUwA==", "license": "MIT", "dependencies": { - "@petamoriken/float16": "^3.4.7", + "@petamoriken/float16": "^3.9.3", "lerc": "^3.0.0", "pako": "^2.0.4", "parse-headers": "^2.0.2", "quick-lru": "^6.1.1", - "web-worker": "^1.2.0", - "xml-utils": "^1.0.2", - "zstddec": "^0.1.0" + "web-worker": "^1.5.0", + "xml-utils": "^1.10.2", + "zstddec": "^0.2.0" }, "engines": { "node": ">=10.19" @@ -3881,7 +3426,7 @@ }, "node_modules/geotiff/node_modules/quick-lru": { "version": "6.1.2", - "resolved": "https://registry.npmmirror.com/quick-lru/-/quick-lru-6.1.2.tgz", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz", "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==", "license": "MIT", "engines": { @@ -3974,9 +3519,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -4289,9 +3834,9 @@ } }, "node_modules/ip-bigint": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/ip-bigint/-/ip-bigint-8.2.4.tgz", - "integrity": "sha512-uLnCfRdjiqRSX36+sKW3PBsotx58qEXSfbRWqdy1N5w6LtlIDCQnxuVce5OIBD50WA9VX2DWixtiBVtalWb1fA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/ip-bigint/-/ip-bigint-8.3.4.tgz", + "integrity": "sha512-W34SH99LpEuCGlX+pv5EM8m57EMfm01o19Os2oQEHsmQVkHcIHtAU/YfpGFTAzUqM65e3GKA1JK/bHhNL1Ag4Q==", "license": "BSD-2-Clause", "engines": { "node": ">=18" @@ -4543,10 +4088,271 @@ }, "node_modules/lerc": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/lerc/-/lerc-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==", "license": "Apache-2.0" }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/local-pkg": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", @@ -4739,20 +4545,41 @@ } }, "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/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==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -5063,9 +4890,9 @@ } }, "node_modules/npm": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.8.0.tgz", - "integrity": "sha512-n19sJeW+RGKdkHo8SCc5xhSwkKhQUFfZaFzSc+EsYXLjSqIV0tl72aDYQVuzVvfrbysGwdaQsNLNy58J10EBSQ==", + "version": "11.12.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.12.1.tgz", + "integrity": "sha512-zcoUuF1kezGSAo0CqtvoLXX3mkRqzuqYdL6Y5tdo8g69NVV3CkjQ6ZBhBgB4d7vGkPcV6TcvLi3GRKPDFX+xTA==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -5083,7 +4910,6 @@ "cacache", "chalk", "ci-info", - "cli-columns", "fastest-levenshtein", "fs-minipass", "glob", @@ -5144,47 +4970,46 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.1.10", - "@npmcli/config": "^10.5.0", + "@npmcli/arborist": "^9.4.2", + "@npmcli/config": "^10.8.1", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", - "@npmcli/package-json": "^7.0.4", + "@npmcli/package-json": "^7.0.5", "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", - "@npmcli/run-script": "^10.0.3", - "@sigstore/tuf": "^4.0.1", + "@npmcli/run-script": "^10.0.4", + "@sigstore/tuf": "^4.0.2", "abbrev": "^4.0.0", "archy": "~1.0.0", - "cacache": "^20.0.3", + "cacache": "^20.0.4", "chalk": "^5.6.2", - "ci-info": "^4.3.1", - "cli-columns": "^4.0.0", + "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^13.0.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", "hosted-git-info": "^9.0.2", "ini": "^6.0.0", - "init-package-json": "^8.2.4", - "is-cidr": "^6.0.1", + "init-package-json": "^8.2.5", + "is-cidr": "^6.0.3", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.0.13", - "libnpmexec": "^10.1.12", - "libnpmfund": "^7.0.13", + "libnpmdiff": "^8.1.5", + "libnpmexec": "^10.2.5", + "libnpmfund": "^7.0.19", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.0.13", + "libnpmpack": "^9.1.5", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", - "make-fetch-happen": "^15.0.3", - "minimatch": "^10.1.1", - "minipass": "^7.1.1", + "make-fetch-happen": "^15.0.5", + "minimatch": "^10.2.4", + "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^12.1.0", + "node-gyp": "^12.2.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", @@ -5194,21 +5019,21 @@ "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", - "pacote": "^21.0.4", + "pacote": "^21.5.0", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", - "semver": "^7.7.3", + "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", - "ssri": "^13.0.0", + "ssri": "^13.0.1", "supports-color": "^10.2.2", - "tar": "^7.5.4", + "tar": "^7.5.11", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", "validate-npm-package-name": "^7.0.2", - "which": "^6.0.0" + "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", @@ -5218,23 +5043,12 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@isaacs/balanced-match": { - "version": "4.0.1", + "node_modules/npm/node_modules/@gar/promise-retry": { + "version": "1.0.3", "inBundle": true, "license": "MIT", "engines": { - "node": "20 || >=22" - } - }, - "node_modules/npm/node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@isaacs/fs-minipass": { @@ -5269,10 +5083,11 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.1.10", + "version": "9.4.2", "inBundle": true, "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", @@ -5315,7 +5130,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.5.0", + "version": "10.8.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -5344,16 +5159,16 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "7.0.1", + "version": "7.0.2", "inBundle": 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", - "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^6.0.0" }, @@ -5422,7 +5237,7 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "7.0.4", + "version": "7.0.5", "inBundle": true, "license": "ISC", "dependencies": { @@ -5432,7 +5247,7 @@ "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", - "validate-npm-package-license": "^3.0.4" + "spdx-expression-parse": "^4.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -5469,7 +5284,7 @@ } }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "10.0.3", + "version": "10.0.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -5477,8 +5292,7 @@ "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", - "proc-log": "^6.0.0", - "which": "^6.0.0" + "proc-log": "^6.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -5496,7 +5310,7 @@ } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "3.1.0", + "version": "3.2.0", "inBundle": true, "license": "Apache-2.0", "engines": { @@ -5512,23 +5326,23 @@ } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "4.1.0", + "version": "4.1.1", "inBundle": true, "license": "Apache-2.0", "dependencies": { + "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", + "@sigstore/core": "^3.2.0", "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.3", - "proc-log": "^6.1.0", - "promise-retry": "^2.0.1" + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "4.0.1", + "version": "4.0.2", "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -5588,14 +5402,6 @@ "node": ">= 14" } }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/aproba": { "version": "2.1.0", "inBundle": true, @@ -5606,6 +5412,14 @@ "inBundle": true, "license": "MIT" }, + "node_modules/npm/node_modules/balanced-match": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/npm/node_modules/bin-links": { "version": "6.0.0", "inBundle": true, @@ -5632,8 +5446,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "5.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/npm/node_modules/cacache": { - "version": "20.0.3", + "version": "20.0.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -5646,8 +5471,7 @@ "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^13.0.0", - "unique-filename": "^5.0.0" + "ssri": "^13.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -5673,7 +5497,7 @@ } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.3.1", + "version": "4.4.0", "funding": [ { "type": "github", @@ -5687,28 +5511,13 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "5.0.1", + "version": "5.0.3", "inBundle": true, "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "5.0.0" - }, "engines": { "node": ">=20" } }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/npm/node_modules/cmd-shim": { "version": "8.0.0", "inBundle": true, @@ -5760,20 +5569,6 @@ "node": ">=0.3.1" } }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", "inBundle": true, @@ -5782,11 +5577,6 @@ "node": ">=6" } }, - "node_modules/npm/node_modules/err-code": { - "version": "2.0.3", - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/exponential-backoff": { "version": "3.1.3", "inBundle": true, @@ -5812,16 +5602,16 @@ } }, "node_modules/npm/node_modules/glob": { - "version": "13.0.0", + "version": "13.0.6", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5873,7 +5663,7 @@ } }, "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", + "version": "0.7.2", "inBundle": true, "license": "MIT", "optional": true, @@ -5882,6 +5672,10 @@ }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/npm/node_modules/ignore-walk": { @@ -5895,14 +5689,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, "node_modules/npm/node_modules/ini": { "version": "6.0.0", "inBundle": true, @@ -5912,7 +5698,7 @@ } }, "node_modules/npm/node_modules/init-package-json": { - "version": "8.2.4", + "version": "8.2.5", "inBundle": true, "license": "ISC", "dependencies": { @@ -5921,7 +5707,6 @@ "promzard": "^3.0.1", "read": "^5.0.1", "semver": "^7.7.2", - "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^7.0.0" }, "engines": { @@ -5936,42 +5721,23 @@ "node": ">= 12" } }, - "node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm/node_modules/is-cidr": { - "version": "6.0.1", + "version": "6.0.3", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "5.0.1" + "cidr-regex": "^5.0.1" }, "engines": { "node": ">=20" } }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/isexe": { - "version": "3.1.1", + "version": "4.0.0", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=20" } }, "node_modules/npm/node_modules/json-parse-even-better-errors": { @@ -6021,11 +5787,11 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.0.13", + "version": "8.1.5", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.10", + "@npmcli/arborist": "^9.4.2", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", @@ -6039,18 +5805,18 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.1.12", + "version": "10.2.5", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.10", + "@gar/promise-retry": "^1.0.0", + "@npmcli/arborist": "^9.4.2", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", "read": "^5.0.1", "semver": "^7.3.7", "signal-exit": "^4.1.0", @@ -6061,11 +5827,11 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.13", + "version": "7.0.19", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.10" + "@npmcli/arborist": "^9.4.2" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -6084,11 +5850,11 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.0.13", + "version": "9.1.5", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.10", + "@npmcli/arborist": "^9.4.2", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" @@ -6154,7 +5920,7 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "11.2.4", + "version": "11.2.7", "inBundle": true, "license": "BlueOak-1.0.0", "engines": { @@ -6162,11 +5928,13 @@ } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "15.0.3", + "version": "15.0.5", "inBundle": 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", @@ -6175,7 +5943,6 @@ "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", "ssri": "^13.0.0" }, "engines": { @@ -6183,23 +5950,23 @@ } }, "node_modules/npm/node_modules/minimatch": { - "version": "10.1.1", + "version": "10.2.4", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", + "version": "7.1.3", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -6216,19 +5983,19 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "5.0.0", + "version": "5.0.2", "inBundle": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", + "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { - "encoding": "^0.1.13" + "iconv-lite": "^0.7.2" } }, "node_modules/npm/node_modules/minipass-flush": { @@ -6253,6 +6020,11 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "inBundle": true, @@ -6275,23 +6047,17 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", + "node_modules/npm/node_modules/minipass-sized": { + "version": "2.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { "node": ">=8" @@ -6330,7 +6096,7 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "12.1.0", + "version": "12.2.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -6341,7 +6107,7 @@ "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.5.2", + "tar": "^7.5.4", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, @@ -6419,7 +6185,7 @@ } }, "node_modules/npm/node_modules/npm-packlist": { - "version": "10.0.3", + "version": "10.0.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -6494,10 +6260,11 @@ } }, "node_modules/npm/node_modules/pacote": { - "version": "21.0.4", + "version": "21.5.0", "inBundle": true, "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", @@ -6511,7 +6278,6 @@ "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" @@ -6537,7 +6303,7 @@ } }, "node_modules/npm/node_modules/path-scurry": { - "version": "2.0.1", + "version": "2.0.2", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -6545,7 +6311,7 @@ "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6595,18 +6361,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm/node_modules/promzard": { "version": "3.0.1", "inBundle": true, @@ -6644,14 +6398,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", "inBundle": true, @@ -6659,7 +6405,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.7.3", + "version": "7.7.4", "inBundle": true, "license": "ISC", "bin": { @@ -6731,24 +6477,6 @@ "node": ">= 14" } }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.5.0", "inBundle": true, @@ -6764,12 +6492,12 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.22", + "version": "3.0.23", "inBundle": true, "license": "CC0-1.0" }, "node_modules/npm/node_modules/ssri": { - "version": "13.0.0", + "version": "13.0.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -6779,30 +6507,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "inBundle": 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/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/supports-color": { "version": "10.2.2", "inBundle": true, @@ -6815,7 +6519,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "7.5.4", + "version": "7.5.11", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -6829,14 +6533,6 @@ "node": ">=18" } }, - "node_modules/npm/node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", "inBundle": true, @@ -6910,51 +6606,11 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/unique-filename": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/unique-slug": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/npm/node_modules/validate-npm-package-name": { "version": "7.0.2", "inBundle": true, @@ -6972,11 +6628,11 @@ } }, "node_modules/npm/node_modules/which": { - "version": "6.0.0", + "version": "6.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" @@ -6986,11 +6642,10 @@ } }, "node_modules/npm/node_modules/write-file-atomic": { - "version": "7.0.0", + "version": "7.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" }, "engines": { @@ -6998,9 +6653,21 @@ } }, "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", + "version": "5.0.0", "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/numcodecs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/numcodecs/-/numcodecs-0.3.2.tgz", + "integrity": "sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==", + "license": "MIT", + "dependencies": { + "fflate": "^0.8.0" + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -7022,16 +6689,17 @@ } }, "node_modules/ol": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ol/-/ol-10.7.0.tgz", - "integrity": "sha512-122U5gamPqNgLpLOkogFJhgpywvd/5en2kETIDW+Ubfi9lPnZ0G9HWRdG+CX0oP8od2d6u6ky3eewIYYlrVczw==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/ol/-/ol-10.8.0.tgz", + "integrity": "sha512-kLk7jIlJvKyhVMAjORTXKjzlM6YIByZ1H/d0DBx3oq8nSPCG6/gbLr5RxukzPgwbhnAqh+xHNCmrvmFKhVMvoQ==", "license": "BSD-2-Clause", "dependencies": { "@types/rbush": "4.0.0", "earcut": "^3.0.0", - "geotiff": "^2.1.3", + "geotiff": "^3.0.2", "pbf": "4.0.1", - "rbush": "^4.0.0" + "rbush": "^4.0.0", + "zarrita": "^0.6.0" }, "funding": { "type": "opencollective", @@ -7165,13 +6833,13 @@ }, "node_modules/pako": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", "license": "(MIT AND Zlib)" }, "node_modules/parse-headers": { "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/parse-headers/-/parse-headers-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==", "license": "MIT" }, @@ -7275,9 +6943,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -7367,9 +7035,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "funding": [ { "type": "opencollective", @@ -7686,6 +7354,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reference-spec-reader": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reference-spec-reader/-/reference-spec-reader-0.2.0.tgz", + "integrity": "sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ==", + "license": "MIT" + }, "node_modules/request-light": { "version": "0.7.0", "resolved": "https://registry.npmmirror.com/request-light/-/request-light-0.7.0.tgz", @@ -7811,47 +7485,47 @@ "node": ">=8.0" } }, - "node_modules/rollup": { - "version": "4.50.2", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.50.2.tgz", - "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.50.2", - "@rollup/rollup-android-arm64": "4.50.2", - "@rollup/rollup-darwin-arm64": "4.50.2", - "@rollup/rollup-darwin-x64": "4.50.2", - "@rollup/rollup-freebsd-arm64": "4.50.2", - "@rollup/rollup-freebsd-x64": "4.50.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", - "@rollup/rollup-linux-arm-musleabihf": "4.50.2", - "@rollup/rollup-linux-arm64-gnu": "4.50.2", - "@rollup/rollup-linux-arm64-musl": "4.50.2", - "@rollup/rollup-linux-loong64-gnu": "4.50.2", - "@rollup/rollup-linux-ppc64-gnu": "4.50.2", - "@rollup/rollup-linux-riscv64-gnu": "4.50.2", - "@rollup/rollup-linux-riscv64-musl": "4.50.2", - "@rollup/rollup-linux-s390x-gnu": "4.50.2", - "@rollup/rollup-linux-x64-gnu": "4.50.2", - "@rollup/rollup-linux-x64-musl": "4.50.2", - "@rollup/rollup-openharmony-arm64": "4.50.2", - "@rollup/rollup-win32-arm64-msvc": "4.50.2", - "@rollup/rollup-win32-ia32-msvc": "4.50.2", - "@rollup/rollup-win32-x64-msvc": "4.50.2", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -8209,9 +7883,9 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -8324,6 +7998,14 @@ "utf8-byte-length": "^1.0.1" } }, + "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==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -8435,6 +8117,18 @@ "url": "https://github.com/sponsors/sxzz" } }, + "node_modules/unzipit": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.3.tgz", + "integrity": "sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg==", + "license": "MIT", + "dependencies": { + "uzip-module": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -8469,6 +8163,12 @@ "uuid": "dist-node/bin/uuid" } }, + "node_modules/uzip-module": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", + "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==", + "license": "MIT" + }, "node_modules/verror": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", @@ -8485,17 +8185,16 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "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", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "bin": { @@ -8512,9 +8211,10 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", - "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -8527,15 +8227,18 @@ "@types/node": { "optional": true }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, "jiti": { "optional": true }, "less": { "optional": true }, - "lightningcss": { - "optional": true - }, "sass": { "optional": true }, @@ -8788,16 +8491,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", - "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz", + "integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.28", - "@vue/compiler-sfc": "3.5.28", - "@vue/runtime-dom": "3.5.28", - "@vue/server-renderer": "3.5.28", - "@vue/shared": "3.5.28" + "@vue/compiler-dom": "3.5.32", + "@vue/compiler-sfc": "3.5.32", + "@vue/runtime-dom": "3.5.32", + "@vue/server-renderer": "3.5.32", + "@vue/shared": "3.5.32" }, "peerDependencies": { "typescript": "*" @@ -8838,14 +8541,14 @@ } }, "node_modules/vue-router": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.2.tgz", - "integrity": "sha512-YFhwaE5c5JcJpNB1arpkl4/GnO32wiUWRB+OEj1T0DlDxEZoOfbltl2xEwktNU/9o1sGcGburIXSpbLpPFe/6w==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.4.tgz", + "integrity": "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg==", "license": "MIT", "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", - "@vue/devtools-api": "^8.0.0", + "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", @@ -8932,7 +8635,7 @@ }, "node_modules/web-worker": { "version": "1.5.0", - "resolved": "https://registry.npmmirror.com/web-worker/-/web-worker-1.5.0.tgz", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", "license": "Apache-2.0" }, @@ -9006,7 +8709,7 @@ }, "node_modules/xml-utils": { "version": "1.10.2", - "resolved": "https://registry.npmmirror.com/xml-utils/-/xml-utils-1.10.2.tgz", + "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz", "integrity": "sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==", "license": "CC0-1.0" }, @@ -9035,9 +8738,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -9088,10 +8791,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zarrita": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/zarrita/-/zarrita-0.6.2.tgz", + "integrity": "sha512-8IV+2bWt5yiHNVK9GVEVK1tscpqDcJj8iz5cIKFOiWiWYUsK4V5njgMtnpkvKu6L7K+Og6zUShd8f+dwb6LvTA==", + "license": "MIT", + "dependencies": { + "@zarrita/storage": "^0.1.4", + "numcodecs": "^0.3.2" + } + }, "node_modules/zstddec": { - "version": "0.1.0", - "resolved": "https://registry.npmmirror.com/zstddec/-/zstddec-0.1.0.tgz", - "integrity": "sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz", + "integrity": "sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==", "license": "MIT AND BSD-3-Clause" } } diff --git a/src/static/app/package.json b/src/static/app/package.json index cdf03fff..81f579ea 100644 --- a/src/static/app/package.json +++ b/src/static/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.3.2", + "version": "4.3.3", "private": true, "type": "module", "module": "es2022", @@ -15,33 +15,33 @@ "@volar/language-server": "2.4.28", "@vue/language-server": "3.2.4", "@vuepic/vue-datepicker": "^12.1.0", - "@vueuse/core": "^14.2.0", + "@vueuse/core": "^14.2.1", "@vueuse/shared": "^14.1.0", "animate.css": "^4.1.1", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.3", - "cidr-tools": "^11.0.8", + "cidr-tools": "^11.3.3", "css-color-converter": "^2.0.0", "dayjs": "^1.11.19", "electron-builder": "^26.7.0", - "fuse.js": "^7.0.0", + "fuse.js": "^7.3.0", "i": "^0.3.7", "is-cidr": "^6.0.3", "npm": "^11.8.0", - "ol": "^10.7.0", + "ol": "^10.8.0", "pinia": "^3.0.4", "pinia-plugin-persistedstate": "^4.7.1", "qrcode": "^1.5.3", "qrcodejs": "^1.0.0", "simple-code-editor": "^2.0.9", "uuid": "^13.0.0", - "vue": "^3.5.28", + "vue": "^3.5.32", "vue-chartjs": "^5.3.3", - "vue-router": "^5.0.2" + "vue-router": "^5.0.4" }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.4", - "vite": "^7.3.1" + "vite": "^8.0.8" }, "overrides": { "tar": "^7.5.6" diff --git a/src/static/app/src/components/clientComponents/clientSettings.vue b/src/static/app/src/components/clientComponents/clientSettings.vue index ae554db4..d872a347 100644 --- a/src/static/app/src/components/clientComponents/clientSettings.vue +++ b/src/static/app/src/components/clientComponents/clientSettings.vue @@ -2,7 +2,7 @@ import { ref, reactive } from "vue" import LocaleText from "@/components/text/localeText.vue"; import OidcSettings from "@/components/clientComponents/clientSettingComponents/oidcSettings.vue"; -import { fetchGet } from "@/utilities/fetch.js" +import { fetchGet, fetchPost } from "@/utilities/fetch.js" const emits = defineEmits(['close']) import { DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore" const dashboardConfigurationStore = DashboardConfigurationStore() @@ -12,12 +12,16 @@ const values = reactive({ }) const toggling = ref(false) -const toggleClientSideApp = async () => { +const updateSettings = async (key: string) => { toggling.value = true - await fetchGet("/api/clients/toggleStatus", {}, (res) => { - values.enableClients = res.data + await fetchPost("/api/updateDashboardConfigurationItem", { + section: "Clients", + key: key, + value: dashboardConfigurationStore.Configuration.Clients[key] + }, async (res) => { + await dashboardConfigurationStore.getConfiguration() + toggling.value = false }) - toggling.value = false } @@ -37,16 +41,43 @@ const toggleClientSideApp = async () => {
- +
+
+
+
+ +
+
+ + +
+
+ + + +
+
+ + + + +
+ diff --git a/src/static/app/src/components/configurationComponents/editConfiguration.vue b/src/static/app/src/components/configurationComponents/editConfiguration.vue index 164af07e..f5d67dc4 100644 --- a/src/static/app/src/components/configurationComponents/editConfiguration.vue +++ b/src/static/app/src/components/configurationComponents/editConfiguration.vue @@ -197,14 +197,14 @@ const deleteConfigurationModal = ref(false) v-model="data[key]" :id="'configuration_' + key"> -
- diff --git a/src/static/app/src/components/configurationComponents/newPeersComponents/notesInput.vue b/src/static/app/src/components/configurationComponents/newPeersComponents/notesInput.vue new file mode 100644 index 00000000..6d12adee --- /dev/null +++ b/src/static/app/src/components/configurationComponents/newPeersComponents/notesInput.vue @@ -0,0 +1,31 @@ + + + + + \ No newline at end of file diff --git a/src/static/app/src/components/configurationComponents/peer.vue b/src/static/app/src/components/configurationComponents/peer.vue index b514463b..4f98f696 100644 --- a/src/static/app/src/components/configurationComponents/peer.vue +++ b/src/static/app/src/components/configurationComponents/peer.vue @@ -134,12 +134,17 @@ export default {
- diff --git a/src/static/app/src/components/configurationComponents/peerAddModal.vue b/src/static/app/src/components/configurationComponents/peerAddModal.vue index f19c4f09..25f8b238 100644 --- a/src/static/app/src/components/configurationComponents/peerAddModal.vue +++ b/src/static/app/src/components/configurationComponents/peerAddModal.vue @@ -15,6 +15,7 @@ import MtuInput from "@/components/configurationComponents/newPeersComponents/mt import PersistentKeepAliveInput from "@/components/configurationComponents/newPeersComponents/persistentKeepAliveInput.vue"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; +import NotesInput from "./newPeersComponents/notesInput.vue"; const dashboardStore = DashboardConfigurationStore() const wireguardStore = WireguardConfigurationsStore() @@ -27,11 +28,11 @@ const peerData = ref({ public_key: "", DNS: dashboardStore.Configuration.Peers.peer_global_dns, endpoint_allowed_ip: dashboardStore.Configuration.Peers.peer_endpoint_allowed_ip, + notes: "", keepalive: parseInt(dashboardStore.Configuration.Peers.peer_keep_alive), mtu: parseInt(dashboardStore.Configuration.Peers.peer_mtu), preshared_key: "", preshared_key_bulkAdd: Boolean(dashboardStore.Configuration.Peers.peer_preshared_key_default), - advanced_security: "off", allowed_ips_validation: true, }) const availableIp = ref([]) @@ -105,6 +106,7 @@ watch(() => { @@ -118,7 +120,7 @@ watch(() => { -