diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..5e2159b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(git -C /c/Users/unknown/Documents/Github/utools log --oneline)", + "Bash(gh api *)", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('rule',{}\\).get\\('description',''\\), '\\\\n', d.get\\('most_recent_instance',{}\\).get\\('location',{}\\), '\\\\n', d.get\\('rule',{}\\).get\\('id',''\\)\\)\")", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); a=d['security_advisory']; print\\(a['summary'], '\\\\nPackage:', d['dependency']['package']['name'], '\\\\nVulnerable:', a['vulnerabilities'][0]['vulnerable_version_range'], '\\\\nFixed:', a['vulnerabilities'][0]['first_patched_version']['identifier'] if a['vulnerabilities'][0].get\\('first_patched_version'\\) else 'N/A', '\\\\nSeverity:', a['severity']\\)\" gh api repos/MrUnknownDE/utools/dependabot/alerts/6)", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); a=d['security_advisory']; print\\(a['summary'], '\\\\nPackage:', d['dependency']['package']['name'], '\\\\nVulnerable:', a['vulnerabilities'][0]['vulnerable_version_range'], '\\\\nFixed:', a['vulnerabilities'][0]['first_patched_version']['identifier'] if a['vulnerabilities'][0].get\\('first_patched_version'\\) else 'N/A', '\\\\nSeverity:', a['severity']\\)\")", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d.get\\('most_recent_instance',{}\\), indent=2\\)\\)\")", + "Bash(npm ls *)" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4a7b821 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,128 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**uTools** is a containerized full-stack web app for IP information and network diagnostics (geolocation, ASN, reverse DNS, ping, traceroute, port scan, WHOIS, DNS, subnet calculator, MAC lookup). Live at https://utools.mrunk.de. + +## Commands + +### Local Development & Deployment + +```bash +# Build and start containers locally +./build.sh + +# Or manually: +docker compose down +export GIT_COMMIT_SHA=$(git rev-parse --short HEAD) +export SENTRY_DSN="" +docker compose -f compose.build.yml build +docker compose -f compose.yml up -d + +# Start only (using pre-built images from Docker Hub) +docker compose up -d + +# View logs +docker compose logs -f backend +docker compose logs -f frontend +``` + +### Backend (local, without Docker) + +```bash +cd backend +cp example.env .env # configure env vars +npm install +npm start # or: node server.js +``` + +No lint or test scripts are configured. + +### Git LFS + +MaxMind databases are stored in Git LFS. After cloning, run: +```bash +git lfs pull +``` + +## Architecture + +### Structure + +``` +utools/ +├── backend/ # Node.js Express API (port 3000) +│ ├── server.js # Entry point: Express setup, Sentry, middleware, route mounting +│ ├── maxmind.js # Singleton MaxMind reader initialization (GeoLite2-City + ASN) +│ ├── utils.js # IP/domain/MAC validation helpers +│ └── routes/ # One file per API endpoint +├── frontend/ # Nginx static server (port 8080) +│ ├── app/ # Vanilla HTML/JS/CSS (no build step) +│ │ ├── index.html / script.js # Main IP info dashboard +│ │ └── *.html # Tool-specific pages (dns, whois, mac, subnet, asn) +│ └── nginx.conf # Clean URL rewrites + /api/* reverse proxy to backend:3000 +├── compose.yml # Production: pulls from Docker Hub +├── compose.build.yml # Build: builds images locally +└── build.sh # Local build + deploy script +``` + +### Request Flow + +``` +Browser → Nginx (port 8080) + ├── static files → frontend/app/ + └── /api/* → Express backend (port 3000) + ├── MaxMind .mmdb files (GeoLite2 from Git LFS) + ├── Sentry (error tracking) + └── System commands (ping, traceroute via exec) +``` + +### API Endpoints + +| Endpoint | Response type | Notes | +|---|---|---| +| `GET /api/ipinfo/:ip` | JSON | Geo + ASN for an IP | +| `GET /api/lookup/:query` | JSON | Resolve domain → IP → geo | +| `GET /api/dns-lookup` | JSON | DNS records | +| `GET /api/whois-lookup` | JSON | WHOIS data | +| `GET /api/ping` | JSON | ICMP ping | +| `GET /api/traceroute` | **SSE** | Streaming hop-by-hop output | +| `GET /api/port-scan` | **SSE** | Streaming port scan results | +| `GET /api/asn-lookup` | JSON | ASN details (cached to filesystem) | +| `GET /api/mac-lookup` | JSON | MAC OUI vendor lookup | +| `GET /api/version` | JSON | Git commit SHA | + +Streaming endpoints use Server-Sent Events (EventSource). Nginx is configured with `proxy_buffering off` for these. + +### Key Implementation Details + +- **Proxy trust:** `app.set('trust proxy', 2)` — backend sits behind Nginx + any upstream proxy. +- **MaxMind readers** are initialized once at startup (`maxmind.js`) and reused across requests. +- **ASN cache** is persisted to `/app/asn-cache` (Docker volume) to reduce external calls. +- **Rate limiting** is configured via env vars (`RATE_LIMIT_MAX`, `RATE_LIMIT_WINDOW_MS`). +- **Private IP detection** (RFC1918, loopback, link-local) is handled in `utils.js` before any lookup. +- **Sentry** is initialized before Express and wraps request/error handlers. +- The backend Dockerfile installs OS packages for `ping` and `traceroute` (`iputils-ping`, `traceroute`). + +### Environment Variables (backend) + +See `backend/example.env`. Key variables: + +| Variable | Default | Purpose | +|---|---|---| +| `GEOIP_CITY_DB` | `./data/GeoLite2-City.mmdb` | Path to MaxMind City DB | +| `GEOIP_ASN_DB` | `./data/GeoLite2-ASN.mmdb` | Path to MaxMind ASN DB | +| `PORT` | `3000` | Express listen port | +| `LOG_LEVEL` | `debug` | Pino log level | +| `PING_COUNT` | `4` | Packets per ping | +| `RATE_LIMIT_MAX` | `200` | Max requests per window | +| `RATE_LIMIT_WINDOW_MS` | `300000` | Rate limit window (5 min) | +| `SENTRY_DSN` | — | Sentry ingest URL | +| `ASN_CACHE_DIR` | — | Directory for ASN response cache | + +### CI/CD + +- **`docker-build-push.yml`**: Triggered on push to `main`. Builds multi-arch images (`linux/amd64`, `linux/arm64`) and pushes to Docker Hub as `mrunknownde/utools-backend` and `mrunknownde/utools-frontend` with `:latest` and `:` tags. Requires LFS checkout for MaxMind databases. +- **`maxmind-update.yml`**: Runs on the 1st of each month. Downloads updated GeoLite2 databases via `geoipupdate` and commits them back to Git LFS. diff --git a/backend/package-lock.json b/backend/package-lock.json index 350c2fe..de9c5ef 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,9 +24,9 @@ } }, "node_modules/@fastify/otel": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.16.0.tgz", - "integrity": "sha512-2304BdM5Q/kUvQC9qJO1KZq3Zn1WWsw+WWkVmFEaj1UE2hEIiuFqrPeglQOwEtw/ftngisqfQ3v70TWMmwhhHA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.18.0.tgz", + "integrity": "sha512-3TASCATfw+ctICSb4ymrv7iCm0qJ0N9CarB+CZ7zIJ7KqNbwI5JjyDL1/sxoC0ccTO1Zyd1iQ+oqncPg5FJXaA==", "funding": [ { "type": "github", @@ -40,18 +40,18 @@ "license": "MIT", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.28.0", - "minimatch": "^10.0.3" + "minimatch": "^10.2.4" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "node_modules/@fastify/otel/node_modules/@opentelemetry/api-logs": { - "version": "0.208.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", - "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -61,13 +61,13 @@ } }, "node_modules/@fastify/otel/node_modules/@opentelemetry/instrumentation": { - "version": "0.208.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", - "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.208.0", - "import-in-the-middle": "^2.0.0", + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", "require-in-the-middle": "^8.0.0" }, "engines": { @@ -77,27 +77,40 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@maxmind/geoip2-node": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-6.0.0.tgz", - "integrity": "sha512-b1zcdxRm13HDgNfHnRpET7E3r1NlSK+VpQhN9dKn8aUUH71Ug6rNmgASYh7WrHDZLDqSXMHryfs5Aw0ufMtBYw==", + "node_modules/@fastify/otel/node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", "dependencies": { - "maxmind": "^4.2.0" + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/@maxmind/geoip2-node": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-6.3.4.tgz", + "integrity": "sha512-BTRFHCX7Uie4wVSPXsWQfg0EVl4eGZgLCts0BTKAP+Eiyt1zmF2UPyuUZkaj0R59XSDYO+84o1THAtaenUoQYg==", + "license": "Apache-2.0", + "dependencies": { + "maxmind": "^5.0.0" } }, "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "license": "Apache-2.0", "engines": { "node": ">=8.0.0" } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.211.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", - "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", + "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -106,22 +119,10 @@ "node": ">=8.0.0" } }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.0.tgz", - "integrity": "sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==", - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@opentelemetry/core": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", - "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.0.tgz", + "integrity": "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" @@ -134,13 +135,13 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.211.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", - "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.214.0.tgz", + "integrity": "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.211.0", - "import-in-the-middle": "^2.0.0", + "@opentelemetry/api-logs": "0.214.0", + "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "engines": { @@ -151,13 +152,13 @@ } }, "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.58.0.tgz", - "integrity": "sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==", + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.61.0.tgz", + "integrity": "sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { @@ -168,13 +169,13 @@ } }, "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.54.0.tgz", - "integrity": "sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.57.0.tgz", + "integrity": "sha512-FMEBChnI4FLN5TE9DHwfH7QpNir1JzXno1uz/TAucVdLCyrG0jTrKIcNHt/i30A0M2AunNBCkcd8Ei26dIPKdg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.38" }, @@ -186,29 +187,12 @@ } }, "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.28.0.tgz", - "integrity": "sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.31.0.tgz", + "integrity": "sha512-f654tZFQXS5YeLDNb9KySrwtg7SnqZN119FauD7acBoTzuLduaiGTNz88ixcVSOOMGZ+EjJu/RFtx5klObC95g==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.59.0.tgz", - "integrity": "sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/instrumentation": "^0.214.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -218,13 +202,13 @@ } }, "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.30.0.tgz", - "integrity": "sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.33.0.tgz", + "integrity": "sha512-sCZWXGalQ01wr3tAhSR9ucqFJ0phidpAle6/17HVjD6gN8FLmZMK/8sKxdXYHy3PbnlV1P4zeiSVFNKpbFMNLA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.214.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -234,12 +218,12 @@ } }, "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.54.0.tgz", - "integrity": "sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.57.0.tgz", + "integrity": "sha512-orhmlaK+ZIW9hKU+nHTbXrCSXZcH83AescTqmpamHRobRmYSQwRbD0a1odc0yAzuzOtxYiHiXAnpnIpaSSY7Ow==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.214.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -249,12 +233,12 @@ } }, "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.58.0.tgz", - "integrity": "sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ==", + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.62.0.tgz", + "integrity": "sha512-3YNuLVPUxafXkH1jBAbGsKNsP3XVzcFDhCDCE3OqBwCwShlqQbLMRMFh1T/d5jaVZiGVmSsfof+ICKD2iOV8xg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.214.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -264,13 +248,13 @@ } }, "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.57.0.tgz", - "integrity": "sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw==", + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.60.0.tgz", + "integrity": "sha512-aNljZKYrEa7obLAxd1bCEDxF7kzCLGXTuTJZ8lMR9rIVEjmuKBXN1gfqpm/OB//Zc2zP4iIve1jBp7sr3mQV6w==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -281,13 +265,13 @@ } }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.211.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.211.0.tgz", - "integrity": "sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.214.0.tgz", + "integrity": "sha512-FlkDhZDRjDJDcO2LcSCtjRpkal1NJ8y0fBqBhTvfAR3JSYY2jAIj1kSS5IjmEBt4c3aWv+u/lqLuoCDrrKCSKg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.5.0", - "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/instrumentation": "0.214.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, @@ -299,9 +283,9 @@ } }, "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", - "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" @@ -314,12 +298,12 @@ } }, "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.59.0.tgz", - "integrity": "sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw==", + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.62.0.tgz", + "integrity": "sha512-ZYt//zcPve8qklaZX+5Z4MkU7UpEkFRrxsf2cnaKYBitqDnsCN69CPAuuMOX6NYdW2rG9sFy7V/QWtBlP5XiNQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.33.0" }, @@ -331,12 +315,12 @@ } }, "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.20.0.tgz", - "integrity": "sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.23.0.tgz", + "integrity": "sha512-4K+nVo+zI+aDz0Z85SObwbdixIbzS9moIuKJaYsdlzcHYnKOPtB7ya8r8Ezivy/GVIBHiKJVq4tv+BEkgOMLaQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "engines": { @@ -347,12 +331,12 @@ } }, "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.55.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.55.0.tgz", - "integrity": "sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ==", + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.58.0.tgz", + "integrity": "sha512-Hc/o8fSsaWxZ8r1Yw4rNDLwTpUopTf4X32y4W6UhlHmW8Wizz8wfhgOKIelSeqFVTKBBPIDUOsQWuIMxBmu8Bw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.1" }, "engines": { @@ -363,13 +347,13 @@ } }, "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.59.0.tgz", - "integrity": "sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg==", + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.62.0.tgz", + "integrity": "sha512-uVip0VuGUQXZ+vFxkKxAUNq8qNl+VFlyHDh/U6IQ8COOEDfbEchdaHnpFrMYF3psZRUuoSIgb7xOeXj00RdwDA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.36.0" }, "engines": { @@ -380,12 +364,12 @@ } }, "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.55.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.55.0.tgz", - "integrity": "sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g==", + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.58.0.tgz", + "integrity": "sha512-6grM3TdMyHzlGY1cUA+mwoPueB1F3dYKgKtZIH6jOFXqfHAByyLTc+6PFjGM9tKh52CFBJaDwodNlL/Td39z7Q==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.214.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -395,12 +379,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.64.0.tgz", - "integrity": "sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.67.0.tgz", + "integrity": "sha512-1WJp5N1lYfHq2IhECOTewFs5Tf2NfUOwQRqs/rZdXKTezArMlucxgzAaqcgp3A3YREXopXTpXHsxZTGHjNhMdQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { @@ -411,13 +395,13 @@ } }, "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.57.0.tgz", - "integrity": "sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg==", + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.60.0.tgz", + "integrity": "sha512-8BahAZpKsOoc+lrZGb7Ofn4g3z8qtp5IxDfvAVpKXsEheQN7ONMH5djT5ihy6yf8yyeQJGS0gXFfpEAEeEHqQg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { @@ -428,12 +412,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.57.0.tgz", - "integrity": "sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q==", + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.60.0.tgz", + "integrity": "sha512-08pO8GFPEIz2zquKDGteBZDNmwketdgH8hTe9rVYgW9kCJXq1Psj3wPQGx+VaX4ZJKCfPeoLMYup9+cxHvZyVQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/mysql": "2.15.27" }, @@ -445,12 +429,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.57.0.tgz", - "integrity": "sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA==", + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.60.0.tgz", + "integrity": "sha512-m/5d3bxQALllCzezYDk/6vajh0tj5OijMMvOZGr+qN1NMXm1dzMNwyJ0gNZW7Fo3YFRyj/jJMxIw+W7d525dlw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@opentelemetry/sql-common": "^0.41.2" }, @@ -462,13 +446,13 @@ } }, "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.63.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.63.0.tgz", - "integrity": "sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg==", + "version": "0.66.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.66.0.tgz", + "integrity": "sha512-KxfLGXBb7k2ueaPJfq2GXBDXBly8P+SpR/4Mj410hhNgmQF3sCqwXvUBQxZQkDAmsdBAoenM+yV1LhtsMRamcA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/sql-common": "^0.41.2", "@types/pg": "8.15.6", @@ -482,12 +466,12 @@ } }, "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.59.0.tgz", - "integrity": "sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg==", + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.62.0.tgz", + "integrity": "sha512-y3pPpot7WzR/8JtHcYlTYsyY8g+pbFhAqbwAuG5bLPnR6v6pt1rQc0DpH0OlGP/9CZbWBP+Zhwp9yFoygf/ZXQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, @@ -499,12 +483,12 @@ } }, "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.30.0.tgz", - "integrity": "sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.33.0.tgz", + "integrity": "sha512-Q6WQwAD01MMTub31GlejoiFACYNw26J426wyjvU7by7fDIr2nZXNW4vhTGs7i7F0TnXBO3xN688g1tdUgYwJ5w==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/tedious": "^4.0.14" }, @@ -515,39 +499,22 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.21.0.tgz", - "integrity": "sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/semantic-conventions": "^1.24.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.7.0" - } - }, "node_modules/@opentelemetry/redis-common": { - "version": "0.38.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", - "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.3.tgz", + "integrity": "sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==", "license": "Apache-2.0", "engines": { "node": "^18.19.0 || >=20.6.0" } }, "node_modules/@opentelemetry/resources": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz", - "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.0.tgz", + "integrity": "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.6.0", + "@opentelemetry/core": "2.7.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -558,13 +525,13 @@ } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.0.tgz", - "integrity": "sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.0.tgz", + "integrity": "sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.6.0", - "@opentelemetry/resources": "2.6.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -598,10 +565,16 @@ "@opentelemetry/api": "^1.1.0" } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, "node_modules/@prisma/instrumentation": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.2.0.tgz", - "integrity": "sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.6.0.tgz", + "integrity": "sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/instrumentation": "^0.207.0" @@ -639,80 +612,87 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@prisma/instrumentation/node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, "node_modules/@sentry/core": { - "version": "10.42.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.42.0.tgz", - "integrity": "sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==", + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.50.0.tgz", + "integrity": "sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@sentry/node": { - "version": "10.42.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.42.0.tgz", - "integrity": "sha512-ZZfU3Fnni7Aj0lTX4e3QpY3UxK4FGuzfM20316UAJycBGnripm+sDHwcekPMGfLnk/FrN9wa1atspVlHvOI0WQ==", + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.50.0.tgz", + "integrity": "sha512-TvwzFQu8MGKzMQ2/tqxcNzFA8UG2kKTB+GDmA4uOzx3+GT849YZRRSJzEXCmYhk1teVd2fbmgqyYY2nyLF5a+Q==", "license": "MIT", "dependencies": { - "@fastify/otel": "0.16.0", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.1", - "@opentelemetry/core": "^2.5.1", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/instrumentation-amqplib": "0.58.0", - "@opentelemetry/instrumentation-connect": "0.54.0", - "@opentelemetry/instrumentation-dataloader": "0.28.0", - "@opentelemetry/instrumentation-express": "0.59.0", - "@opentelemetry/instrumentation-fs": "0.30.0", - "@opentelemetry/instrumentation-generic-pool": "0.54.0", - "@opentelemetry/instrumentation-graphql": "0.58.0", - "@opentelemetry/instrumentation-hapi": "0.57.0", - "@opentelemetry/instrumentation-http": "0.211.0", - "@opentelemetry/instrumentation-ioredis": "0.59.0", - "@opentelemetry/instrumentation-kafkajs": "0.20.0", - "@opentelemetry/instrumentation-knex": "0.55.0", - "@opentelemetry/instrumentation-koa": "0.59.0", - "@opentelemetry/instrumentation-lru-memoizer": "0.55.0", - "@opentelemetry/instrumentation-mongodb": "0.64.0", - "@opentelemetry/instrumentation-mongoose": "0.57.0", - "@opentelemetry/instrumentation-mysql": "0.57.0", - "@opentelemetry/instrumentation-mysql2": "0.57.0", - "@opentelemetry/instrumentation-pg": "0.63.0", - "@opentelemetry/instrumentation-redis": "0.59.0", - "@opentelemetry/instrumentation-tedious": "0.30.0", - "@opentelemetry/instrumentation-undici": "0.21.0", - "@opentelemetry/resources": "^2.5.1", - "@opentelemetry/sdk-trace-base": "^2.5.1", - "@opentelemetry/semantic-conventions": "^1.39.0", - "@prisma/instrumentation": "7.2.0", - "@sentry/core": "10.42.0", - "@sentry/node-core": "10.42.0", - "@sentry/opentelemetry": "10.42.0", - "import-in-the-middle": "^2.0.6" + "@fastify/otel": "0.18.0", + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/core": "^2.6.1", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/instrumentation-amqplib": "0.61.0", + "@opentelemetry/instrumentation-connect": "0.57.0", + "@opentelemetry/instrumentation-dataloader": "0.31.0", + "@opentelemetry/instrumentation-fs": "0.33.0", + "@opentelemetry/instrumentation-generic-pool": "0.57.0", + "@opentelemetry/instrumentation-graphql": "0.62.0", + "@opentelemetry/instrumentation-hapi": "0.60.0", + "@opentelemetry/instrumentation-http": "0.214.0", + "@opentelemetry/instrumentation-ioredis": "0.62.0", + "@opentelemetry/instrumentation-kafkajs": "0.23.0", + "@opentelemetry/instrumentation-knex": "0.58.0", + "@opentelemetry/instrumentation-koa": "0.62.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.58.0", + "@opentelemetry/instrumentation-mongodb": "0.67.0", + "@opentelemetry/instrumentation-mongoose": "0.60.0", + "@opentelemetry/instrumentation-mysql": "0.60.0", + "@opentelemetry/instrumentation-mysql2": "0.60.0", + "@opentelemetry/instrumentation-pg": "0.66.0", + "@opentelemetry/instrumentation-redis": "0.62.0", + "@opentelemetry/instrumentation-tedious": "0.33.0", + "@opentelemetry/sdk-trace-base": "^2.6.1", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@prisma/instrumentation": "7.6.0", + "@sentry/core": "10.50.0", + "@sentry/node-core": "10.50.0", + "@sentry/opentelemetry": "10.50.0", + "import-in-the-middle": "^3.0.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/node-core": { - "version": "10.42.0", - "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.42.0.tgz", - "integrity": "sha512-9tf3fPV6M071aps72D+PEtdQPTuj+SuqO2+PpTfdPP5ZL4TTKYo3VK0li76SL+5wGdTFGV5qmsokHq9IRBA0iA==", + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.50.0.tgz", + "integrity": "sha512-Eb1BYf4Lc7ZYmdX3acKP6SgyGikrBA370gbGHaWI5jRu7G7vig8sIu1ghPmY5AlvqBPOetado7GniXr6fAXbTw==", "license": "MIT", "dependencies": { - "@sentry/core": "10.42.0", - "@sentry/opentelemetry": "10.42.0", - "import-in-the-middle": "^2.0.6" + "@sentry/core": "10.50.0", + "@sentry/opentelemetry": "10.50.0", + "import-in-the-middle": "^3.0.0" }, "engines": { "node": ">=18" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1", - "@opentelemetry/resources": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" }, @@ -720,16 +700,13 @@ "@opentelemetry/api": { "optional": true }, - "@opentelemetry/context-async-hooks": { - "optional": true - }, "@opentelemetry/core": { "optional": true }, - "@opentelemetry/instrumentation": { + "@opentelemetry/exporter-trace-otlp-http": { "optional": true }, - "@opentelemetry/resources": { + "@opentelemetry/instrumentation": { "optional": true }, "@opentelemetry/sdk-trace-base": { @@ -741,19 +718,18 @@ } }, "node_modules/@sentry/opentelemetry": { - "version": "10.42.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.42.0.tgz", - "integrity": "sha512-5vsYz683iihzlIj3sT1+tEixf0awwXK86a+aYsnMHrTXJDrkBDq4U0ZT+yxdPfJlkaxRtYycFR08SXr2pSm7Eg==", + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.50.0.tgz", + "integrity": "sha512-axn3pgDPveGdaMUC0abMCmFN7ux2pA5ebPufCef4lMIsyg7BBQvaEJ+vE19wjstMaBCAJGsdZlL3eeP2rtgRMw==", "license": "MIT", "dependencies": { - "@sentry/core": "10.42.0" + "@sentry/core": "10.50.0" }, "engines": { "node": ">=18" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" @@ -778,12 +754,12 @@ } }, "node_modules/@types/node": { - "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/pg": { @@ -819,6 +795,7 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -852,6 +829,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -860,6 +838,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -873,12 +852,14 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -893,9 +874,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -906,7 +887,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -916,54 +897,10 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "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" @@ -985,6 +922,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -997,6 +935,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -1012,6 +951,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "license": "MIT", "dependencies": { "no-case": "^2.2.0", "upper-case": "^1.1.1" @@ -1021,6 +961,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1029,6 +970,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.1.0.tgz", "integrity": "sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==", + "license": "MIT", "dependencies": { "camel-case": "^3.0.0", "constant-case": "^2.0.0", @@ -1060,6 +1002,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -1070,6 +1013,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1080,17 +1024,20 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" }, "node_modules/constant-case": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", "integrity": "sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==", + "license": "MIT", "dependencies": { "snake-case": "^2.1.0", "upper-case": "^1.1.1" @@ -1100,6 +1047,7 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -1111,39 +1059,48 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", "engines": { "node": "*" } @@ -1152,6 +1109,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -1160,6 +1118,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1167,12 +1126,14 @@ "node_modules/dedent-js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", - "integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==" + "integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==", + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1181,6 +1142,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -1190,14 +1152,16 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", "integrity": "sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==", + "license": "MIT", "dependencies": { "no-case": "^2.2.0" } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -1209,6 +1173,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -1221,25 +1186,29 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -1248,6 +1217,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1256,6 +1226,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1264,6 +1235,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -1274,12 +1246,14 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1331,9 +1305,10 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", "engines": { "node": ">= 16" }, @@ -1341,7 +1316,7 @@ "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" + "express": ">= 4.11" } }, "node_modules/express/node_modules/qs": { @@ -1360,34 +1335,29 @@ } }, "node_modules/fast-copy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" - }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "engines": { - "node": ">=6" - } + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.3.tgz", + "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==", + "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -1398,6 +1368,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -1410,6 +1381,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1424,6 +1396,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1432,6 +1405,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1440,6 +1414,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -1448,6 +1423,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -1471,6 +1447,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -1483,6 +1460,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1494,6 +1472,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1502,9 +1481,10 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -1516,6 +1496,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", "integrity": "sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==", + "license": "MIT", "dependencies": { "no-case": "^2.2.0", "upper-case": "^1.1.3" @@ -1524,26 +1505,33 @@ "node_modules/help-me": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" }, "node_modules/html-entities": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==" + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", + "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { @@ -1559,30 +1547,31 @@ } }, "node_modules/import-in-the-middle": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", - "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.1.tgz", + "integrity": "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==", "license": "Apache-2.0", "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" } }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", "engines": { "node": ">= 12" } @@ -1591,6 +1580,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -1599,6 +1589,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -1607,6 +1598,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", "integrity": "sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==", + "license": "MIT", "dependencies": { "lower-case": "^1.1.0" } @@ -1615,6 +1607,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", "integrity": "sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==", + "license": "MIT", "dependencies": { "upper-case": "^1.1.0" } @@ -1623,19 +1616,16 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -1646,12 +1636,14 @@ "node_modules/lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "license": "MIT" }, "node_modules/lower-case-first": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", "integrity": "sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==", + "license": "MIT", "dependencies": { "lower-case": "^1.1.2" } @@ -1663,25 +1655,28 @@ "license": "MIT" }, "node_modules/macaddress": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.5.3.tgz", - "integrity": "sha512-vGBKTA+jwM4KgjGZ+S/8/Mkj9rWzePyGY6jManXPGhiWu63RYwW8dKPyk5koP+8qNVhPhHgFa1y/MJ4wrjsNrg==" + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.5.4.tgz", + "integrity": "sha512-i8xVWoUjj2woYU8kbpQby86Kq7uF7xl2brtKREXUBWpfgqx1fKXEeYzDiVMVxA/IufC1d3xxwJRHtFCX+9IspA==", + "license": "MIT" }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/maxmind": { - "version": "4.3.24", - "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-4.3.24.tgz", - "integrity": "sha512-dexrLcjfS2xDGOvdV8XcfQYmyQVpGidMwEG2ld19lXlsB+i+lXRWPzQi81HfwRXR4hxzFr5gT0oAIFyqAAb/Ww==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-5.0.6.tgz", + "integrity": "sha512-5bvd/u+kIaTqaGM+xkXjatzQw1dQfSmlLggr2W1EKMyMxSgx2woZyusLpNpZ4DdPmL+1bbJWeo4LXsi6bC0Iew==", + "license": "MIT", "dependencies": { - "mmdb-lib": "2.1.1", - "tiny-lru": "11.2.11" + "mmdb-lib": "3.0.2", + "tiny-lru": "13.0.0" }, "engines": { "node": ">=12", @@ -1692,6 +1687,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1700,6 +1696,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -1708,6 +1705,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1716,6 +1714,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -1727,6 +1726,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1735,6 +1735,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -1743,12 +1744,12 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -1761,14 +1762,16 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/mmdb-lib": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-2.1.1.tgz", - "integrity": "sha512-yx8H/1H5AfnufiLnzzPqPf4yr/dKU9IFT1rPVwSkrKWHsQEeVVd6+X+L0nUbXhlEFTu3y/7hu38CFmEVgzvyeg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-3.0.2.tgz", + "integrity": "sha512-7e87vk0DdWT647wjcfEtWeMtjm+zVGqNohN/aeIymbUfjHQ2T4Sx5kM+1irVDBSloNC3CkGKxswdMoo8yhqTDg==", + "license": "MIT", "engines": { "node": ">=10", "npm": ">=6" @@ -1783,12 +1786,14 @@ "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1797,6 +1802,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "license": "MIT", "dependencies": { "lower-case": "^1.1.1" } @@ -1805,6 +1811,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1813,6 +1820,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1824,6 +1832,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -1832,6 +1841,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -1843,6 +1853,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -1851,6 +1862,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -1865,6 +1877,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -1876,6 +1889,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1884,6 +1898,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "license": "MIT", "dependencies": { "no-case": "^2.2.0" } @@ -1892,6 +1907,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1900,6 +1916,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", "integrity": "sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==", + "license": "MIT", "dependencies": { "camel-case": "^3.0.0", "upper-case-first": "^1.1.0" @@ -1909,6 +1926,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz", "integrity": "sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==", + "license": "MIT", "dependencies": { "no-case": "^2.2.0" } @@ -1917,14 +1935,16 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -1958,16 +1978,17 @@ } }, "node_modules/pino": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", - "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", "dependencies": { + "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", @@ -1982,37 +2003,49 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", "dependencies": { "split2": "^4.0.0" } }, "node_modules/pino-pretty": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz", - "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==", + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "license": "MIT", "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", - "fast-copy": "^3.0.2", + "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", + "pino-abstract-transport": "^3.0.0", "pump": "^3.0.0", - "secure-json-parse": "^2.4.0", + "secure-json-parse": "^4.0.0", "sonic-boom": "^4.0.1", - "strip-json-comments": "^3.1.1" + "strip-json-comments": "^5.0.2" }, "bin": { "pino-pretty": "bin.js" } }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" }, "node_modules/postgres-array": { "version": "2.0.0", @@ -2054,9 +2087,9 @@ } }, "node_modules/process-warning": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", - "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", "funding": [ { "type": "github", @@ -2066,12 +2099,14 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "MIT" }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -2081,26 +2116,19 @@ } }, "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -2115,12 +2143,14 @@ "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2140,39 +2170,11 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -2181,6 +2183,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2224,7 +2227,8 @@ "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" }, "node_modules/safe-buffer": { "version": "5.2.1", @@ -2243,12 +2247,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-stable-stringify": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", "engines": { "node": ">=10" } @@ -2260,64 +2266,71 @@ "license": "MIT" }, "node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/sentence-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", "integrity": "sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==", + "license": "MIT", "dependencies": { "no-case": "^2.2.0", "upper-case-first": "^1.1.2" } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -2326,17 +2339,20 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -2352,12 +2368,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -2370,6 +2387,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -2387,6 +2405,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -2405,6 +2424,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -2414,16 +2434,18 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", "integrity": "sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==", + "license": "MIT", "dependencies": { "no-case": "^2.2.0" } }, "node_modules/socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -2432,9 +2454,10 @@ } }, "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" } @@ -2443,19 +2466,16 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", "engines": { "node": ">= 10.x" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" - }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2464,6 +2484,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2477,6 +2498,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2485,11 +2507,12 @@ } }, "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2499,6 +2522,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", "integrity": "sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==", + "license": "MIT", "dependencies": { "lower-case": "^1.1.1", "upper-case": "^1.1.1" @@ -2508,22 +2532,25 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", "dependencies": { "real-require": "^0.2.0" } }, "node_modules/tiny-lru": { - "version": "11.2.11", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.11.tgz", - "integrity": "sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-13.0.0.tgz", + "integrity": "sha512-xDHxKKS1FdF0Tv2P+QT7IeSEg74K/8cEDzbv3Tv6UyHHUgBOjOiQiBp818MGj66dhurQus/IBcoAbwIKtSGc6Q==", + "license": "BSD-3-Clause", "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/title-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", "integrity": "sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==", + "license": "MIT", "dependencies": { "no-case": "^2.2.0", "upper-case": "^1.0.3" @@ -2533,6 +2560,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -2541,6 +2569,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -2556,15 +2585,16 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2572,12 +2602,14 @@ "node_modules/upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==" + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "license": "MIT" }, "node_modules/upper-case-first": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", "integrity": "sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==", + "license": "MIT", "dependencies": { "upper-case": "^1.1.1" } @@ -2586,6 +2618,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -2594,6 +2627,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2601,14 +2635,15 @@ "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" }, "node_modules/whois": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/whois/-/whois-2.14.2.tgz", - "integrity": "sha512-JzH7/WUC4L59hPKwc6lZ59OpeBDcG+axt9vBYeQg1DCtrlwyxTUzorhI58nEWHmN+R/RtiUi9MdQ6NE9TmPREQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/whois/-/whois-2.16.1.tgz", + "integrity": "sha512-gCxr+knAuXzsHJlHaIi8bCZoRU9anysyKbo0mgjAkkanNdOvUhBZShgG/ckcpKryDUVs2cH684MU+wOLjBYlAA==", + "license": "FreeBSD", "dependencies": { - "punycode": "^2.3.1", "socks": "^2.2.2", "underscore": "^1.9.1", "yargs": "^15.4.1" @@ -2621,6 +2656,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/whois-json/-/whois-json-2.0.4.tgz", "integrity": "sha512-ui9Qe0qS4yWtFAPw0q+QPuuH+m4vDDtoO/YtqmF00YoPMMNplhya7SqTVAxThWIVXhWHPEKajFEulegmysdlwQ==", + "license": "MIT", "dependencies": { "change-case": "^3.0.2", "dedent-js": "^1.0.1", @@ -2632,6 +2668,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -2644,7 +2681,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/xtend": { "version": "4.0.2", @@ -2658,12 +2696,14 @@ "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -2685,6 +2725,7 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" diff --git a/backend/utils.js b/backend/utils.js index 999b27f..0a489d4 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -287,7 +287,6 @@ function parseTracerouteLine(line) { * @returns {Promise<{port: number, status: 'open'|'closed'|'timeout', service: string}>} A promise that resolves with the port status. */ function checkPort(port, host, timeout = 2000) { - // A small map of common ports to their services const commonPorts = { 21: 'FTP', 22: 'SSH', 23: 'Telnet', 25: 'SMTP', 53: 'DNS', 80: 'HTTP', 110: 'POP3', 143: 'IMAP', 443: 'HTTPS', 445: 'SMB', 993: 'IMAPS', @@ -296,45 +295,24 @@ function checkPort(port, host, timeout = 2000) { }; const service = commonPorts[port] || 'Unknown'; - return new Promise((resolve, reject) => { - // DEFENSE IN DEPTH: Prevent scanning of private IPs at the function level - if (!isValidIp(host) || isPrivateIp(host)) { - const error = new Error(`Scanning restricted: ${host} is not a valid public IP.`); - logger.warn({ host, port }, "Blocked attempt to scan restricted IP in checkPort"); - return resolve({ - port, - status: 'error', - service, - error: 'Restricted IP', - details: 'Scanning private or invalid IPs is not allowed.' - }); - } + // Validate before any network operation — throw so CodeQL tracks this as a hard barrier + if (!isValidIp(host) || isPrivateIp(host)) { + logger.warn({ host, port }, 'Blocked attempt to scan restricted IP in checkPort'); + throw new Error(`Scanning restricted: ${host} is not a valid public IP.`); + } + return new Promise((resolve) => { const socket = new net.Socket(); socket.setTimeout(timeout); - socket.on('connect', () => { - socket.destroy(); - resolve({ port, status: 'open', service }); - }); - - socket.on('timeout', () => { - socket.destroy(); - resolve({ port, status: 'timeout', service }); - }); - + socket.on('connect', () => { socket.destroy(); resolve({ port, status: 'open', service }); }); + socket.on('timeout', () => { socket.destroy(); resolve({ port, status: 'timeout', service }); }); socket.on('error', (err) => { socket.destroy(); - // 'ECONNREFUSED' is the key for a closed port. Other errors might be network issues. const status = err.code === 'ECONNREFUSED' ? 'closed' : 'error'; resolve({ port, status, service, error: err.code }); }); - // Explicit inline guard (defence-in-depth; also satisfies CodeQL SSRF dataflow) - if (!isValidIp(host) || isPrivateIp(host)) { - socket.destroy(); - return resolve({ port, status: 'error', service, error: 'Restricted IP' }); - } socket.connect(port, host); }); } diff --git a/compose.yml b/compose.yml index 28224d8..811a7e4 100644 --- a/compose.yml +++ b/compose.yml @@ -4,6 +4,7 @@ services: image: mrunknownde/utools-backend container_name: utools_backend restart: unless-stopped + user: "0" # Run as root so ASN cache volume is writable environment: NODE_ENV: production PORT: 3000 diff --git a/frontend/app/asn-lookup.html b/frontend/app/asn-lookup.html deleted file mode 100644 index 1a13825..0000000 --- a/frontend/app/asn-lookup.html +++ /dev/null @@ -1,471 +0,0 @@ - - - - - - - ASN / AS Lookup - uTools - - - - - - - - - -
-

uTools Network - Suite

- -
- -
- -

AS / ASN Lookup

-

Peering graph, prefixes & IXP connections for any - Autonomous System

- - -
- - -
- - - - - - - - - - - - -
- - - - - \ No newline at end of file diff --git a/frontend/app/asn-lookup.js b/frontend/app/asn-lookup.js deleted file mode 100644 index 9a1f40a..0000000 --- a/frontend/app/asn-lookup.js +++ /dev/null @@ -1,399 +0,0 @@ -// frontend/app/asn-lookup.js -'use strict'; - -const API_BASE = '/api'; - -// ─── State ──────────────────────────────────────────────────────────────────── -let currentData = null; -let showAllPrefixes = false; - -// ─── DOM Refs ───────────────────────────────────────────────────────────────── -const asnInput = document.getElementById('asn-input'); -const lookupButton = document.getElementById('lookup-button'); -const errorBox = document.getElementById('error-box'); -const loadingSection = document.getElementById('loading-section'); -const loadingMsg = document.getElementById('loading-msg'); -const resultsSection = document.getElementById('results-section'); - -// ─── Helpers ────────────────────────────────────────────────────────────────── -function showError(msg) { - errorBox.textContent = msg; - errorBox.classList.remove('hidden'); - loadingSection.classList.add('hidden'); - resultsSection.classList.add('hidden'); - if (window._loadingHintTimer) clearTimeout(window._loadingHintTimer); -} -function hideError() { errorBox.classList.add('hidden'); } - -function setLoading(msg = 'Querying RIPE Stat & PeeringDB…') { - hideError(); - loadingMsg.textContent = msg; - loadingSection.classList.remove('hidden'); - resultsSection.classList.add('hidden'); - - // After 3s show a hint that large ASes can be slow - if (window._loadingHintTimer) clearTimeout(window._loadingHintTimer); - window._loadingHintTimer = setTimeout(() => { - const hint = document.getElementById('loading-hint'); - if (hint) hint.classList.remove('hidden'); - }, 3000); -} - -function updateUrlParam(asn) { - const url = new URL(window.location); - url.searchParams.set('asn', asn); - window.history.pushState({}, '', url); -} - -// ─── Main Lookup ────────────────────────────────────────────────────────────── -async function doLookup(rawAsn) { - const asn = String(rawAsn || '').trim().toUpperCase().replace(/^AS/, ''); - if (!asn || isNaN(Number(asn))) { - showError('Please enter a valid ASN (e.g. 15169 or AS3320).'); - return; - } - - setLoading('Querying RIPE Stat & PeeringDB…'); - updateUrlParam(asn); - - try { - const res = await fetch(`${API_BASE}/asn-lookup?asn=${encodeURIComponent(asn)}`); - const data = await res.json(); - - if (!res.ok || !data.success) { - showError(data.error || `Request failed (HTTP ${res.status})`); - return; - } - - currentData = data; - renderResults(data); - - } catch (err) { - showError(`Network error: ${err.message}`); - } -} - -// ─── Render ─────────────────────────────────────────────────────────────────── -function renderResults(data) { - // Show results FIRST so the graph container has real dimensions (clientWidth > 0) - loadingSection.classList.add('hidden'); - resultsSection.classList.remove('hidden'); - // Reset loading hint for next lookup - if (window._loadingHintTimer) clearTimeout(window._loadingHintTimer); - const hint = document.getElementById('loading-hint'); - if (hint) hint.classList.add('hidden'); - - // Header - document.getElementById('res-asn').textContent = `AS${data.asn}`; - document.getElementById('res-name').textContent = data.name || 'Unknown'; - - const announcedBadge = document.getElementById('res-announced-badge'); - if (announcedBadge) { - if (data.announced) announcedBadge.classList.remove('hidden'); - else announcedBadge.classList.add('hidden'); - } - - const typeBadge = document.getElementById('res-type-badge'); - if (typeBadge) { - typeBadge.textContent = data.type || ''; - typeBadge.classList.toggle('hidden', !data.type); - } - - const peeringPolicy = data.peeringdb?.peeringPolicy; - const policyContainer = document.getElementById('res-policy-container'); - const policyEl = document.getElementById('res-policy'); - if (policyContainer && policyEl) { - if (peeringPolicy) { - policyEl.textContent = peeringPolicy; - policyContainer.classList.remove('hidden'); - } else { - policyContainer.classList.add('hidden'); - } - } - - const website = data.peeringdb?.website; - const websiteContainer = document.getElementById('res-website-container'); - const websiteEl = document.getElementById('res-website'); - if (websiteContainer && websiteEl) { - if (website) { - websiteEl.href = website; - websiteEl.textContent = website.replace(/^https?:\/\//, '').replace(/\/$/, ''); - websiteContainer.classList.remove('hidden'); - } else { - websiteContainer.classList.add('hidden'); - } - } - - // Rich Info Grid - const richInfo = document.getElementById('res-rich-info'); - let hasRichInfo = false; - - const fields = [ - { id: 'type', value: data.peeringdb?.infoType }, - { id: 'scope', value: data.peeringdb?.infoScope }, - { id: 'traffic', value: data.peeringdb?.infoTraffic }, - { id: 'ratio', value: data.peeringdb?.infoRatio } - ]; - - fields.forEach(f => { - const container = document.getElementById(`res-info-${f.id}-container`); - const el = document.getElementById(`res-info-${f.id}`); - if (container && el) { - if (f.value) { - el.textContent = f.value; - container.classList.remove('hidden'); - hasRichInfo = true; - } else { - container.classList.add('hidden'); - } - } - }); - - if (richInfo) { - if (hasRichInfo) richInfo.classList.remove('hidden'); - else richInfo.classList.add('hidden'); - } - - document.getElementById('res-upstream-count').textContent = data.graph?.level2?.upstreams?.length ?? '?'; - document.getElementById('res-downstream-count').textContent = data.graph?.level2?.downstreams?.length ?? '?'; - document.getElementById('res-prefix-count').textContent = data.prefixes?.length ?? '?'; - - // Prefixes + IXPs (before graph — these are cheap) - renderPrefixes(data.prefixes); - renderIxps(data.peeringdb?.ixps); - renderNeighbourTable('upstream-table', data.graph?.level2?.upstreams ?? [], 'blue'); - renderNeighbourTable('downstream-table', data.graph?.level2?.downstreams ?? [], 'green'); - - // Graph LAST — needs the container to be visible for clientWidth - if (data.graph) renderGraph(data.graph); -} - -// ─── Prefix List ───────────────────────────────────────────────────────────── -function renderPrefixes(prefixes) { - const list = document.getElementById('prefix-list'); - const empty = document.getElementById('prefix-empty'); - const toggle = document.getElementById('prefix-toggle'); - - if (!prefixes || prefixes.length === 0) { - list?.classList.add('hidden'); - empty?.classList.remove('hidden'); - toggle?.classList.add('hidden'); - return; - } - empty?.classList.add('hidden'); - toggle?.classList.remove('hidden'); - - const toShow = showAllPrefixes ? prefixes : prefixes.slice(0, 20); - if (list) list.innerHTML = toShow.map(p => `${p}`).join(''); - if (toggle) toggle.textContent = showAllPrefixes ? 'Show less' : `Show all (${prefixes.length})`; -} - -document.getElementById('prefix-toggle').addEventListener('click', () => { - showAllPrefixes = !showAllPrefixes; - if (currentData) renderPrefixes(currentData.prefixes); -}); - -// ─── IXP List ───────────────────────────────────────────────────────────────── -function renderIxps(ixps) { - const list = document.getElementById('ixp-list'); - const empty = document.getElementById('ixp-empty'); - - if (!ixps || ixps.length === 0) { - if (list) list.innerHTML = ''; - empty?.classList.remove('hidden'); - return; - } - empty?.classList.add('hidden'); - if (list) { - list.innerHTML = ixps.map(ix => ` -
- ${ix.name} - ${ix.speed >= 1000 ? `${ix.speed / 1000}G` : `${ix.speed}M`} -
- `).join(''); - } -} - -// ─── Neighbour Table ────────────────────────────────────────────────────────── -function renderNeighbourTable(elId, nodes, colour) { - const el = document.getElementById(elId); - if (!nodes || nodes.length === 0) { - el.innerHTML = `

None reported.

`; - return; - } - const colClass = colour === 'blue' ? 'text-blue-400' : 'text-green-400'; - el.innerHTML = nodes.map(n => ` -
- AS${n.asn} - ${n.name || '—'} - ${n.power ? `pwr:${n.power}` : ''} -
- `).join(''); -} - -// ─── D3 Network Graph ───────────────────────────────────────────────────────── -function renderGraph(graph) { - const container = document.getElementById('graph-container'); - const svg = d3.select('#graph-svg'); - svg.selectAll('*').remove(); - - const W = container.clientWidth; - const H = container.clientHeight; - - // ── Build nodes & links ─────────────────────────────────────────────────── - const nodeMap = new Map(); - - function addNode(asn, name, role) { - const key = String(asn); - if (!nodeMap.has(key)) nodeMap.set(key, { id: key, asn, name: name || `AS${asn}`, role }); - } - - addNode(graph.center.asn, graph.center.name, 'center'); - - // Limit graph nodes to top 15 to prevent Physics Engine crash & unreadable hairball - const vizUpstreams = graph.level2.upstreams.slice(0, 15); - const vizDownstreams = graph.level2.downstreams.slice(0, 15); - - vizUpstreams.forEach(n => addNode(n.asn, n.name, 'upstream')); - vizDownstreams.forEach(n => addNode(n.asn, n.name, 'downstream')); - graph.level3.forEach(d => { - d.upstreams.forEach(n => addNode(n.asn, n.name, 'tier1')); - }); - - const nodes = Array.from(nodeMap.values()); - - const links = []; - const centerId = String(graph.center.asn); - - vizUpstreams.forEach(n => { - links.push({ source: String(n.asn), target: centerId, type: 'upstream', power: n.power || 1 }); - }); - vizDownstreams.forEach(n => { - links.push({ source: centerId, target: String(n.asn), type: 'downstream', power: n.power || 1 }); - }); - graph.level3.forEach(d => { - d.upstreams.forEach(n => { - links.push({ source: String(n.asn), target: String(d.parentAsn), type: 'tier1', power: n.power || 1 }); - }); - }); - - // Remove duplicate links - const uniqueLinks = Array.from( - new Map(links.map(l => [`${l.source}-${l.target}`, l])).values() - ); - - // ── Layer X positions (fixed horizontal layout) ──────────────────────── - const layerX = { tier1: W * 0.08, upstream: W * 0.3, center: W * 0.55, downstream: W * 0.8 }; - - nodes.forEach(n => { - n.fx = layerX[n.role] ?? W / 2; - }); - - // ── Power → stroke width ────────────────────────────────────────────── - const maxPower = Math.max(...uniqueLinks.map(l => l.power), 1); - const strokeScale = d3.scaleLinear().domain([0, maxPower]).range([0.5, 4]); - - // ── Node size ───────────────────────────────────────────────────────── - const nodeRadius = { center: 20, upstream: 11, downstream: 11, tier1: 8 }; - - // ── Simulation ──────────────────────────────────────────────────────── - const sim = d3.forceSimulation(nodes) - .force('link', d3.forceLink(uniqueLinks).id(d => d.id).distance(d => { - if (d.type === 'tier1') return 90; - if (d.type === 'upstream') return 130; - return 110; - }).strength(0.6)) - .force('charge', d3.forceManyBody().strength(-220)) - .force('y', d3.forceY(H / 2).strength(0.04)) - .force('collide', d3.forceCollide().radius(d => nodeRadius[d.role] + 14)) - .alphaDecay(0.025); - - // ── Zoom/Pan ────────────────────────────────────────────────────────── - const g = svg.append('g'); - svg.call(d3.zoom().scaleExtent([0.3, 3]).on('zoom', evt => g.attr('transform', evt.transform))); - - // ── Draw links ──────────────────────────────────────────────────────── - const link = g.append('g').selectAll('line') - .data(uniqueLinks).join('line') - .attr('class', d => `link link-${d.type}`) - .attr('stroke-width', d => strokeScale(d.power)); - - // ── Draw nodes ──────────────────────────────────────────────────────── - const tooltip = document.getElementById('graph-tooltip'); - - const node = g.append('g').selectAll('g') - .data(nodes).join('g') - .attr('class', d => `node node-${d.role}`) - .style('cursor', 'pointer') - .on('click', (_, d) => { - if (d.role !== 'center') window.location.href = `/asn?asn=${d.asn}`; - }) - .on('mouseenter', (evt, d) => { - tooltip.style.opacity = '1'; - tooltip.innerHTML = ` - AS${d.asn}
- ${d.name}
- ${d.role === 'tier1' ? 'Tier-1 / Transit' : d.role} - `; - }) - .on('mousemove', (evt) => { - const rect = container.getBoundingClientRect(); - let x = evt.clientX - rect.left + 14; - let y = evt.clientY - rect.top - 10; - if (x + 230 > W) x = x - 244; - tooltip.style.left = x + 'px'; - tooltip.style.top = y + 'px'; - }) - .on('mouseleave', () => { tooltip.style.opacity = '0'; }) - .call(d3.drag() - .on('start', (evt, d) => { if (!evt.active) sim.alphaTarget(0.3).restart(); d.fy = d.y; }) - .on('drag', (evt, d) => { d.fy = evt.y; }) - .on('end', (evt, d) => { if (!evt.active) sim.alphaTarget(0); d.fy = null; }) - ); - - node.append('circle').attr('r', d => nodeRadius[d.role]); - - // Label: 2 lines (ASN + name truncated) - node.append('text') - .attr('dy', d => nodeRadius[d.role] + 13) - .attr('font-size', d => d.role === 'center' ? 12 : 9) - .attr('fill', '#e5e7eb') - .text(d => `AS${d.asn}`); - - // Name label for ALL roles (tier1 gets shorter truncation) - node.append('text') - .attr('dy', d => nodeRadius[d.role] + 23) - .attr('font-size', d => d.role === 'tier1' ? 7 : 8) - .attr('fill', d => d.role === 'tier1' ? '#6b7280' : '#9ca3af') - .text(d => { - if (!d.name) return ''; - const max = d.role === 'center' ? 22 : d.role === 'tier1' ? 12 : 16; - return d.name.length > max ? d.name.slice(0, max) + '…' : d.name; - }); - - // ── Tick ────────────────────────────────────────────────────────────── - sim.on('tick', () => { - // Clamp Y so nodes don't fly off - nodes.forEach(n => { n.y = Math.max(30, Math.min(H - 30, n.y)); }); - - link - .attr('x1', d => d.source.x) - .attr('y1', d => d.source.y) - .attr('x2', d => d.target.x) - .attr('y2', d => d.target.y); - - node.attr('transform', d => `translate(${d.x},${d.y})`); - }); -} - -// ─── Initialise ─────────────────────────────────────────────────────────────── -lookupButton.addEventListener('click', () => doLookup(asnInput.value)); -asnInput.addEventListener('keypress', e => { if (e.key === 'Enter') doLookup(asnInput.value); }); - -// Auto-lookup from URL -const urlParam = new URLSearchParams(window.location.search).get('asn'); -if (urlParam) { - asnInput.value = urlParam; - doLookup(urlParam); -} diff --git a/frontend/app/dns-lookup.html b/frontend/app/dns-lookup.html deleted file mode 100644 index 04901a7..0000000 --- a/frontend/app/dns-lookup.html +++ /dev/null @@ -1,252 +0,0 @@ - - - - - - - DNS Lookup - uTools - - - - - - - - -
-

uTools Network - Suite

- -
- -
- -

DNS Lookup

- - -
-
- - - -
- - -
- - - - - - - -
- - - - - - \ No newline at end of file diff --git a/frontend/app/dns-lookup.js b/frontend/app/dns-lookup.js deleted file mode 100644 index 34c8437..0000000 --- a/frontend/app/dns-lookup.js +++ /dev/null @@ -1,188 +0,0 @@ -// frontend/dns-lookup.js -document.addEventListener('DOMContentLoaded', () => { - // --- DOM Elements (DNS Lookup) --- - const dnsDomainInput = document.getElementById('dns-domain-input'); - const dnsTypeSelect = document.getElementById('dns-type-select'); - const dnsLookupButton = document.getElementById('dns-lookup-button'); - const dnsLookupErrorEl = document.getElementById('dns-lookup-error'); - const dnsLookupResultsSection = document.getElementById('dns-lookup-results-section'); - const dnsLookupQueryEl = document.getElementById('dns-lookup-query'); - const dnsLookupLoader = document.getElementById('dns-lookup-loader'); - const dnsLookupOutputEl = document.getElementById('dns-lookup-output'); - - // --- DOM Elements (Common) --- - const globalErrorEl = document.getElementById('global-error'); - const commitShaEl = document.getElementById('commit-sha'); - - // --- Configuration --- - const API_BASE_URL = '/api'; // Anpassen, falls nötig - - // --- Helper Functions --- - - /** Zeigt globale Fehler an */ - function showGlobalError(message) { - if (!globalErrorEl) return; - globalErrorEl.textContent = `Error: ${message}`; - globalErrorEl.classList.remove('hidden'); - } - - /** Versteckt globale Fehler */ - function hideGlobalError() { - if (!globalErrorEl) return; - globalErrorEl.classList.add('hidden'); - } - - /** - * Generische Funktion zum Abrufen und Anzeigen von Lookup-Ergebnissen. - * @param {string} endpoint - Der API-Endpunkt (z.B. '/dns-lookup'). - * @param {object} params - Query-Parameter als Objekt (z.B. { domain: '...', type: '...' }). - * @param {HTMLElement} resultsSection - Der Container für die Ergebnisse. - * @param {HTMLElement} loaderElement - Das Loader-Element. - * @param {HTMLElement} errorElement - Das Fehleranzeige-Element für diesen Lookup. - * @param {HTMLElement} queryElement - Das Element zur Anzeige der Suchanfrage. - * @param {HTMLElement} outputElement - Das Element zur Anzeige der Ergebnisse (
 oder 

). - * @param {function} displayFn - Funktion zur Formatierung und Anzeige der Daten im outputElement. - */ - async function fetchAndDisplay(endpoint, params, resultsSection, loaderElement, errorElement, queryElement, outputElement, displayFn) { - // Reset animation - resultsSection.classList.remove('fade-in'); - void resultsSection.offsetWidth; // Trigger reflow - resultsSection.classList.add('fade-in'); - - resultsSection.classList.remove('hidden'); - loaderElement.classList.remove('hidden'); - errorElement.classList.add('hidden'); - outputElement.textContent = ''; // Clear previous results - if (queryElement) queryElement.textContent = Object.values(params).join(', '); // Display query - hideGlobalError(); // Hide global errors before new request - - const urlParams = new URLSearchParams(params); - const url = `${API_BASE_URL}${endpoint}?${urlParams.toString()}`; - - try { - const response = await fetch(url); - const data = await response.json(); - - if (!response.ok || !data.success) { - throw new Error(data.error || `Request failed with status ${response.status}`); - } - - console.log(`Received ${endpoint} data:`, data); - displayFn(data, outputElement); // Call the specific display function - - } catch (error) { - console.error(`Failed to fetch ${endpoint}:`, error); - errorElement.textContent = `Error: ${error.message}`; - errorElement.classList.remove('hidden'); - outputElement.textContent = ''; // Clear output on error - } finally { - loaderElement.classList.add('hidden'); - } - } - - /** Ruft die Versionsinformationen (Commit SHA) ab */ - async function fetchVersionInfo() { - if (!commitShaEl) return; // Don't fetch if element doesn't exist - try { - const response = await fetch(`${API_BASE_URL}/version`); - if (!response.ok) throw new Error(`Network response: ${response.statusText} (${response.status})`); - const data = await response.json(); - commitShaEl.textContent = data.commitSha || 'unknown'; - } catch (error) { - console.error('Failed to fetch version info:', error); - commitShaEl.textContent = 'error'; - // Optionally show global error - // showGlobalError(`Could not load version info: ${error.message}`); - } - } - - // --- URL Parameter Functions --- - - /** - * Updates the URL with DNS lookup parameters - * @param {string} domain - The domain name - * @param {string} type - The DNS record type - */ - function updateUrlParams(domain, type) { - const url = new URL(window.location); - url.searchParams.set('domain', domain); - url.searchParams.set('type', type); - window.history.pushState({}, '', url); - } - - /** - * Reads URL parameters and returns domain and type if present - * @returns {object|null} Object with domain and type, or null if not present - */ - function getUrlParams() { - const urlParams = new URLSearchParams(window.location.search); - const domain = urlParams.get('domain'); - const type = urlParams.get('type'); - - if (domain && type) { - return { domain, type }; - } - return null; - } - - // --- DNS Lookup Specific Functions --- - function displayDnsResults(data, outputEl) { - if (!data.records || Object.keys(data.records).length === 0) { - outputEl.textContent = 'No records found for this domain and type.'; - return; - } - // Format output as JSON string for simplicity - outputEl.textContent = JSON.stringify(data.records, null, 2); - } - - function handleDnsLookupClick() { - const domain = dnsDomainInput.value.trim(); - const type = dnsTypeSelect.value; - if (!domain) { - dnsLookupErrorEl.textContent = 'Please enter a domain name.'; - dnsLookupErrorEl.classList.remove('hidden'); - return; - } - - // Update URL with parameters - updateUrlParams(domain, type); - - fetchAndDisplay( - '/dns-lookup', - { domain, type }, - dnsLookupResultsSection, - dnsLookupLoader, - dnsLookupErrorEl, - dnsLookupQueryEl, - dnsLookupOutputEl, - displayDnsResults - ); - } - - /** - * Executes DNS lookup from URL parameters if they exist - */ - function executeLookupFromUrl() { - const params = getUrlParams(); - if (params) { - // Populate the input fields - dnsDomainInput.value = params.domain; - dnsTypeSelect.value = params.type; - - // Trigger the lookup - handleDnsLookupClick(); - } - } - - // --- Initial Load & Event Listeners --- - fetchVersionInfo(); // Lade Versionsinfo für Footer - - dnsLookupButton.addEventListener('click', handleDnsLookupClick); - dnsDomainInput.addEventListener('keypress', (event) => { - if (event.key === 'Enter') handleDnsLookupClick(); - }); - - // Execute lookup from URL parameters if present - executeLookupFromUrl(); - -}); // End DOMContentLoaded \ No newline at end of file diff --git a/frontend/app/index.html b/frontend/app/index.html index d933416..6aedd6c 100644 --- a/frontend/app/index.html +++ b/frontend/app/index.html @@ -1,580 +1,43 @@ - - - - IP Info & Network Tools - uTools - - - - - - + + + uTools – Network Suite + + + + - +

+

+ uTools Network Suite +

+ +
-
-

uTools Network - Suite

- -
+
-
+ -

Your Digital Footprint

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

Your Public IP

-
-
- -
-
- - -
-
-

- Location Details

-
-
- -
-
- -
-

- Network (ASN)

-
-
- -
-
-
- - -
-

- Hostname (rDNS)

-
-
- -
-
-
- - -
-

- - - - Location Visualization -

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

- - - - IP Address / Domain Lookup -

-
- - -
- - - - -
- - - - - - - - - - - - - - -
- - - - - + + + + - - \ No newline at end of file + diff --git a/frontend/app/mac-lookup.html b/frontend/app/mac-lookup.html deleted file mode 100644 index 91154d3..0000000 --- a/frontend/app/mac-lookup.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - MAC Vendor Lookup - uTools - - - - - - - - -
-

uTools Network - Suite

- -
- -
- -

MAC Address Vendor Lookup

- -
-
- - -
- - -
- - - - - -
- - - - - \ No newline at end of file diff --git a/frontend/app/mac-lookup.js b/frontend/app/mac-lookup.js deleted file mode 100644 index 9c5fc7e..0000000 --- a/frontend/app/mac-lookup.js +++ /dev/null @@ -1,82 +0,0 @@ -// frontend/app/mac-lookup.js -document.addEventListener('DOMContentLoaded', () => { - const macInput = document.getElementById('mac-input'); - const macLookupButton = document.getElementById('mac-lookup-button'); - const macLookupErrorEl = document.getElementById('mac-lookup-error'); - const macLookupResultsSection = document.getElementById('mac-lookup-results-section'); - const macLookupQueryEl = document.getElementById('mac-lookup-query'); - const macLookupLoader = document.getElementById('mac-lookup-loader'); - const macLookupOutputEl = document.getElementById('mac-lookup-output'); - const commitShaEl = document.getElementById('commit-sha'); - const globalErrorEl = document.getElementById('global-error'); - - const API_BASE_URL = '/api'; - - function showGlobalError(message) { - if (!globalErrorEl) return; - globalErrorEl.textContent = `Error: ${message}`; - globalErrorEl.classList.remove('hidden'); - } - - async function fetchVersionInfo() { - if (!commitShaEl) return; - try { - const response = await fetch(`${API_BASE_URL}/version`); - if (!response.ok) throw new Error(`Network response: ${response.statusText}`); - const data = await response.json(); - commitShaEl.textContent = data.commitSha || 'unknown'; - } catch (error) { - console.error('Failed to fetch version info:', error); - commitShaEl.textContent = 'error'; - } - } - - function displayMacResult(data, outputEl) { - outputEl.textContent = data.vendor || 'No vendor found.'; - } - - async function handleMacLookup() { - const mac = macInput.value.trim(); - if (!mac) { - macLookupErrorEl.textContent = 'Please enter a MAC address.'; - macLookupErrorEl.classList.remove('hidden'); - return; - } - - // Reset animation - macLookupResultsSection.classList.remove('fade-in'); - void macLookupResultsSection.offsetWidth; // Trigger reflow - macLookupResultsSection.classList.add('fade-in'); - - macLookupResultsSection.classList.remove('hidden'); - macLookupLoader.classList.remove('hidden'); - macLookupErrorEl.classList.add('hidden'); - macLookupOutputEl.textContent = ''; - macLookupQueryEl.textContent = mac; - - try { - const response = await fetch(`${API_BASE_URL}/mac-lookup?mac=${encodeURIComponent(mac)}`); - const data = await response.json(); - - if (!response.ok || !data.success) { - throw new Error(data.error || `Request failed with status ${response.status}`); - } - - displayMacResult(data, macLookupOutputEl); - - } catch (error) { - console.error('Failed to fetch MAC vendor:', error); - macLookupErrorEl.textContent = `Error: ${error.message}`; - macLookupErrorEl.classList.remove('hidden'); - macLookupOutputEl.textContent = ''; - } finally { - macLookupLoader.classList.add('hidden'); - } - } - - fetchVersionInfo(); - macLookupButton.addEventListener('click', handleMacLookup); - macInput.addEventListener('keypress', (event) => { - if (event.key === 'Enter') handleMacLookup(); - }); -}); \ No newline at end of file diff --git a/frontend/app/pages/asn.js b/frontend/app/pages/asn.js new file mode 100644 index 0000000..11b06ca --- /dev/null +++ b/frontend/app/pages/asn.js @@ -0,0 +1,349 @@ +import { API } from '../shared.js'; + +export const page = { + title: 'ASN Lookup', + + template: () => ` +
+

AS / ASN Lookup

+

Peering graph, prefixes & IXP connections for any Autonomous System

+ +
+ + +
+ + + + + + +
`, + + async init(search) { + const asnInput = document.getElementById('asn-input'); + const lookupButton = document.getElementById('lookup-button'); + const errorBox = document.getElementById('error-box'); + const loadingSection = document.getElementById('loading-section'); + const resultsSection = document.getElementById('results-section'); + + let currentData = null; + let showAllPrefixes = false; + + const syncBtn = () => { lookupButton.disabled = !asnInput.value.trim(); }; + asnInput.addEventListener('input', syncBtn); + + function showError(msg) { + errorBox.textContent = msg; + errorBox.classList.toggle('hidden', !msg); + loadingSection.classList.add('hidden'); + resultsSection.classList.add('hidden'); + } + + function setLoading(msg) { + errorBox.classList.add('hidden'); + document.getElementById('loading-msg').textContent = msg; + loadingSection.classList.remove('hidden'); + resultsSection.classList.add('hidden'); + } + + async function doLookup(rawAsn) { + const asn = String(rawAsn || '').trim().toUpperCase().replace(/^AS/, ''); + if (!asn || isNaN(Number(asn))) { showError('Please enter a valid ASN (e.g. 15169 or AS3320).'); return; } + + setLoading('Querying RIPE Stat & PeeringDB…'); + const url = new URL(location.href); + url.searchParams.set('asn', asn); + history.replaceState({}, '', url); + + try { + const res = await fetch(`${API}/asn-lookup?asn=${encodeURIComponent(asn)}`); + const data = await res.json(); + if (!res.ok || !data.success) { showError(data.error || `Request failed (HTTP ${res.status})`); return; } + currentData = data; + renderResults(data); + } catch (err) { + showError(`Network error: ${err.message}`); + } + } + + function renderResults(data) { + loadingSection.classList.add('hidden'); + resultsSection.classList.remove('hidden'); + + document.getElementById('res-asn').textContent = `AS${data.asn}`; + document.getElementById('res-name').textContent = data.name || 'Unknown'; + + const announcedBadge = document.getElementById('res-announced-badge'); + announcedBadge.classList.toggle('hidden', !data.announced); + + const typeBadge = document.getElementById('res-type-badge'); + typeBadge.textContent = data.type || ''; + typeBadge.classList.toggle('hidden', !data.type); + + const policyContainer = document.getElementById('res-policy-container'); + const policyEl = document.getElementById('res-policy'); + if (data.peeringdb?.peeringPolicy) { + policyEl.textContent = data.peeringdb.peeringPolicy; + policyContainer.classList.remove('hidden'); + } else { policyContainer.classList.add('hidden'); } + + const websiteContainer = document.getElementById('res-website-container'); + const websiteEl = document.getElementById('res-website'); + if (data.peeringdb?.website) { + websiteEl.href = data.peeringdb.website; + websiteEl.textContent = data.peeringdb.website.replace(/^https?:\/\//, '').replace(/\/$/, ''); + websiteContainer.classList.remove('hidden'); + } else { websiteContainer.classList.add('hidden'); } + + const richInfo = document.getElementById('res-rich-info'); + let hasRich = false; + [['type', data.peeringdb?.infoType], ['scope', data.peeringdb?.infoScope], + ['traffic', data.peeringdb?.infoTraffic], ['ratio', data.peeringdb?.infoRatio]].forEach(([id, val]) => { + const c = document.getElementById(`res-info-${id}-container`); + const e = document.getElementById(`res-info-${id}`); + if (c && e) { if (val) { e.textContent = val; c.classList.remove('hidden'); hasRich = true; } else c.classList.add('hidden'); } + }); + richInfo.classList.toggle('hidden', !hasRich); + + document.getElementById('res-upstream-count').textContent = data.graph?.level2?.upstreams?.length ?? '?'; + document.getElementById('res-downstream-count').textContent = data.graph?.level2?.downstreams?.length ?? '?'; + document.getElementById('res-prefix-count').textContent = data.prefixes?.length ?? '?'; + + renderPrefixes(data.prefixes); + renderIxps(data.peeringdb?.ixps); + renderNeighbourTable('upstream-table', data.graph?.level2?.upstreams ?? [], 'blue'); + renderNeighbourTable('downstream-table', data.graph?.level2?.downstreams ?? [], 'green'); + if (data.graph) renderGraph(data.graph); + } + + function renderPrefixes(prefixes) { + const list = document.getElementById('prefix-list'); + const empty = document.getElementById('prefix-empty'); + const toggle = document.getElementById('prefix-toggle'); + if (!prefixes?.length) { list.classList.add('hidden'); empty.classList.remove('hidden'); toggle.classList.add('hidden'); return; } + empty.classList.add('hidden'); toggle.classList.remove('hidden'); + const toShow = showAllPrefixes ? prefixes : prefixes.slice(0, 20); + list.innerHTML = toShow.map(p => `${p}`).join(''); + toggle.textContent = showAllPrefixes ? 'Show less' : `Show all (${prefixes.length})`; + } + + document.getElementById('prefix-toggle').addEventListener('click', () => { + showAllPrefixes = !showAllPrefixes; + if (currentData) renderPrefixes(currentData.prefixes); + }); + + function renderIxps(ixps) { + const list = document.getElementById('ixp-list'); + const empty = document.getElementById('ixp-empty'); + if (!ixps?.length) { list.innerHTML = ''; empty.classList.remove('hidden'); return; } + empty.classList.add('hidden'); + list.innerHTML = ixps.map(ix => ` +
+ ${ix.name} + ${ix.speed >= 1000 ? ix.speed / 1000 + 'G' : ix.speed + 'M'} +
`).join(''); + } + + function renderNeighbourTable(elId, nodes, colour) { + const el = document.getElementById(elId); + if (!nodes?.length) { el.innerHTML = '

None reported.

'; return; } + const col = colour === 'blue' ? 'text-blue-400' : 'text-green-400'; + el.innerHTML = nodes.map(n => ` +
+ AS${n.asn} + ${n.name || '—'} + ${n.power ? 'pwr:' + n.power : ''} +
`).join(''); + } + + function renderGraph(graph) { + const container = document.getElementById('graph-container'); + const svg = d3.select('#graph-svg'); + svg.selectAll('*').remove(); + const W = container.clientWidth, H = container.clientHeight; + + const nodeMap = new Map(); + function addNode(asn, name, role) { + const key = String(asn); + if (!nodeMap.has(key)) nodeMap.set(key, { id: key, asn, name: name || `AS${asn}`, role }); + } + addNode(graph.center.asn, graph.center.name, 'center'); + const vizUp = graph.level2.upstreams.slice(0, 15); + const vizDown = graph.level2.downstreams.slice(0, 15); + vizUp.forEach(n => addNode(n.asn, n.name, 'upstream')); + vizDown.forEach(n => addNode(n.asn, n.name, 'downstream')); + graph.level3.forEach(d => d.upstreams.forEach(n => addNode(n.asn, n.name, 'tier1'))); + + const nodes = Array.from(nodeMap.values()); + const links = []; + const cid = String(graph.center.asn); + vizUp.forEach(n => links.push({ source: String(n.asn), target: cid, type: 'upstream', power: n.power || 1 })); + vizDown.forEach(n => links.push({ source: cid, target: String(n.asn), type: 'downstream', power: n.power || 1 })); + graph.level3.forEach(d => d.upstreams.forEach(n => links.push({ source: String(n.asn), target: String(d.parentAsn), type: 'tier1', power: n.power || 1 }))); + const uniqueLinks = Array.from(new Map(links.map(l => [`${l.source}-${l.target}`, l])).values()); + + const layerX = { tier1: W * 0.08, upstream: W * 0.3, center: W * 0.55, downstream: W * 0.8 }; + nodes.forEach(n => { n.fx = layerX[n.role] ?? W / 2; }); + + const maxPow = Math.max(...uniqueLinks.map(l => l.power), 1); + const strokeSc = d3.scaleLinear().domain([0, maxPow]).range([0.5, 4]); + const nodeRadius = { center: 20, upstream: 11, downstream: 11, tier1: 8 }; + + const sim = d3.forceSimulation(nodes) + .force('link', d3.forceLink(uniqueLinks).id(d => d.id).distance(d => d.type === 'tier1' ? 90 : 120).strength(0.6)) + .force('charge', d3.forceManyBody().strength(-220)) + .force('y', d3.forceY(H / 2).strength(0.04)) + .force('collide', d3.forceCollide().radius(d => nodeRadius[d.role] + 14)) + .alphaDecay(0.025); + + const g = svg.append('g'); + svg.call(d3.zoom().scaleExtent([0.3, 3]).on('zoom', evt => g.attr('transform', evt.transform))); + + const link = g.append('g').selectAll('line').data(uniqueLinks).join('line') + .attr('class', d => `link link-${d.type}`) + .attr('stroke-width', d => strokeSc(d.power)); + + const tooltip = document.getElementById('graph-tooltip'); + const node = g.append('g').selectAll('g').data(nodes).join('g') + .attr('class', d => `node node-${d.role}`) + .style('cursor', 'pointer') + .on('click', (_, d) => { if (d.role !== 'center') window._router.navigate('/asn', { asn: d.asn }); }) + .on('mouseenter', (_, d) => { + tooltip.style.opacity = '1'; + tooltip.innerHTML = `AS${d.asn}
${d.name}
${d.role === 'tier1' ? 'Tier-1 / Transit' : d.role}`; + }) + .on('mousemove', evt => { + const r = container.getBoundingClientRect(); + let x = evt.clientX - r.left + 14, y = evt.clientY - r.top - 10; + if (x + 230 > W) x -= 244; + tooltip.style.left = x + 'px'; tooltip.style.top = y + 'px'; + }) + .on('mouseleave', () => { tooltip.style.opacity = '0'; }) + .call(d3.drag() + .on('start', (evt, d) => { if (!evt.active) sim.alphaTarget(0.3).restart(); d.fy = d.y; }) + .on('drag', (evt, d) => { d.fy = evt.y; }) + .on('end', (evt, d) => { if (!evt.active) sim.alphaTarget(0); d.fy = null; })); + + node.append('circle').attr('r', d => nodeRadius[d.role]); + node.append('text').attr('dy', d => nodeRadius[d.role] + 13).attr('font-size', d => d.role === 'center' ? 12 : 9).text(d => `AS${d.asn}`); + node.append('text').attr('dy', d => nodeRadius[d.role] + 23) + .attr('font-size', d => d.role === 'tier1' ? 7 : 8) + .attr('fill', d => d.role === 'tier1' ? '#6b7280' : '#9ca3af') + .text(d => { + if (!d.name) return ''; + const max = d.role === 'center' ? 22 : d.role === 'tier1' ? 12 : 16; + return d.name.length > max ? d.name.slice(0, max) + '…' : d.name; + }); + + sim.on('tick', () => { + nodes.forEach(n => { n.y = Math.max(30, Math.min(H - 30, n.y)); }); + link.attr('x1', d => d.source.x).attr('y1', d => d.source.y).attr('x2', d => d.target.x).attr('y2', d => d.target.y); + node.attr('transform', d => `translate(${d.x},${d.y})`); + }); + } + + lookupButton.addEventListener('click', () => doLookup(asnInput.value)); + asnInput.addEventListener('keypress', e => { if (e.key === 'Enter' && !lookupButton.disabled) doLookup(asnInput.value); }); + + const params = new URLSearchParams(search); + const urlAsn = params.get('asn'); + if (urlAsn) { asnInput.value = urlAsn; syncBtn(); doLookup(urlAsn); } + } +}; diff --git a/frontend/app/pages/dns.js b/frontend/app/pages/dns.js new file mode 100644 index 0000000..5ce36d6 --- /dev/null +++ b/frontend/app/pages/dns.js @@ -0,0 +1,102 @@ +import { API, setupCopyBtn, showError } from '../shared.js'; + +export const page = { + title: 'DNS Lookup', + + template: () => ` +
+

DNS Lookup

+ +
+
+ + + +
+ + + +
+ + +
`, + + async init(search) { + const input = document.getElementById('dns-domain-input'); + const select = document.getElementById('dns-type-select'); + const btn = document.getElementById('dns-lookup-button'); + const errorEl = document.getElementById('dns-lookup-error'); + const section = document.getElementById('dns-lookup-results-section'); + const queryEl = document.getElementById('dns-lookup-query'); + const loader = document.getElementById('dns-lookup-loader'); + const output = document.getElementById('dns-lookup-output'); + const copyBtn = document.getElementById('copy-dns-btn'); + + const syncBtn = () => { btn.disabled = !input.value.trim(); }; + input.addEventListener('input', syncBtn); + + setupCopyBtn(copyBtn, () => output.textContent); + + async function doLookup() { + const domain = input.value.trim(); + const type = select.value; + if (!domain) return; + + const url = new URL(location.href); + url.searchParams.set('domain', domain); + url.searchParams.set('type', type); + history.replaceState({}, '', url); + + showError(errorEl, null); + section.classList.remove('hidden'); + loader.classList.remove('hidden'); + output.textContent = ''; + queryEl.textContent = `${domain} (${type})`; + + try { + const r = await fetch(`${API}/dns-lookup?domain=${encodeURIComponent(domain)}&type=${encodeURIComponent(type)}`); + const data = await r.json(); + if (!r.ok || !data.success) throw new Error(data.error || `HTTP ${r.status}`); + output.textContent = JSON.stringify(data.records, null, 2); + } catch (err) { + showError(errorEl, err.message); + output.textContent = ''; + } finally { + loader.classList.add('hidden'); + } + } + + btn.addEventListener('click', doLookup); + input.addEventListener('keypress', e => { if (e.key === 'Enter' && !btn.disabled) doLookup(); }); + + const params = new URLSearchParams(search); + const d = params.get('domain'); + if (d) { + input.value = d; + const t = params.get('type'); + if (t) select.value = t; + syncBtn(); + doLookup(); + } + } +}; diff --git a/frontend/app/pages/home.js b/frontend/app/pages/home.js new file mode 100644 index 0000000..dec58ca --- /dev/null +++ b/frontend/app/pages/home.js @@ -0,0 +1,671 @@ +import { API, setupCopyBtn } from '../shared.js'; + +export const page = { + title: 'IP Info & Tools', + + template: () => ` +
+

Your Digital Footprint

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

Your Public IP

+
+
+ + +
+
+ +
+
+

Location Details

+
+
+ +
+
+
+

Network (ASN)

+
+
+ +
+
+
+ +
+

Hostname (rDNS)

+
+
+ +
+
+
+ + +
+

+ + + + Location Visualization +

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

+ + + + IP Address / Domain Lookup +

+
+ + +
+ + + +
+ + + + + + + + +
`, + + async init(search) { + // ── DOM refs ────────────────────────────────────────────────── + const ipAddressLink = document.getElementById('ip-address-link'); + const ipAddressSpan = document.getElementById('ip-address'); + const copyIpBtn = document.getElementById('copy-ip-btn'); + const ipLoader = document.getElementById('ip-loader'); + const geoLoader = document.getElementById('geo-loader'); + const asnLoader = document.getElementById('asn-loader'); + const rdnsLoader = document.getElementById('rdns-loader'); + const mapLoader = document.getElementById('map-loader'); + const mapEl = document.getElementById('map'); + const mapMessage = document.getElementById('map-message'); + const globalError = document.getElementById('global-error'); + + const lookupInput = document.getElementById('lookup-ip-input'); + const lookupBtn = document.getElementById('lookup-button'); + const lookupErrorEl = document.getElementById('lookup-error'); + const lookupSection = document.getElementById('lookup-results-section'); + const lookupIpEl = document.getElementById('lookup-ip-address'); + const copyLookupIpBtn = document.getElementById('copy-lookup-ip-btn'); + const lookupResLoader = document.getElementById('lookup-result-loader'); + const lookupMapEl = document.getElementById('lookup-map'); + const lookupMapLoader = document.getElementById('lookup-map-loader'); + const lookupMapMsg = document.getElementById('lookup-map-message'); + const lookupPingBtn = document.getElementById('lookup-ping-button'); + const lookupTraceBtn = document.getElementById('lookup-trace-button'); + const lookupScanBtn = document.getElementById('lookup-scan-button'); + const lookupPingRes = document.getElementById('lookup-ping-results'); + const lookupPingLoader= document.getElementById('lookup-ping-loader'); + const lookupPingOutput= document.getElementById('lookup-ping-output'); + const lookupPingError = document.getElementById('lookup-ping-error'); + + const tracerouteSection = document.getElementById('traceroute-section'); + const tracerouteOutput = document.querySelector('#traceroute-output pre'); + const tracerouteLoader = document.getElementById('traceroute-loader'); + const tracerouteMessage = document.getElementById('traceroute-message'); + const tracerouteStopBtn = document.getElementById('traceroute-stop-btn'); + + const portScanSection = document.getElementById('port-scan-section'); + const portScanOutput = document.getElementById('port-scan-output'); + const portScanLoader = document.getElementById('port-scan-loader'); + const portScanMessage = document.getElementById('port-scan-message'); + const portScanStopBtn = document.getElementById('port-scan-stop-btn'); + + // ── State ──────────────────────────────────────────────────── + let map = null, lookupMap = null, currentIp = null, currentLookupIp = null; + let eventSource = null, portScanEventSource = null; + + // ── Helpers ────────────────────────────────────────────────── + function showGlobalErr(msg) { + if (!globalError) return; + globalError.textContent = `Error: ${msg}`; + globalError.classList.remove('hidden'); + } + + function isValidIp(input) { + const v4 = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/; + const v6 = /^(?:(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,7}:|(?:[a-fA-F0-9]{1,4}:){1,6}:[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,5}(?::[a-fA-F0-9]{1,4}){1,2}|(?:[a-fA-F0-9]{1,4}:){1,4}(?::[a-fA-F0-9]{1,4}){1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}(?::[a-fA-F0-9]{1,4}){1,4}|(?:[a-fA-F0-9]{1,4}:){1,2}(?::[a-fA-F0-9]{1,4}){1,5}|[a-fA-F0-9]{1,4}:(?::[a-fA-F0-9]{1,4}){1,6}|:(?::[a-fA-F0-9]{1,4}){1,7}|::)$/; + return v4.test(input) || v6.test(input); + } + + function updateField(el, val, loaderEl = null, errorEl = null, def = '-') { + if (loaderEl) loaderEl.classList.add('hidden'); + if (errorEl) errorEl.textContent = ''; + const container = el?.closest('div:not(.loader)'); + if (container?.classList.contains('hidden')) container.classList.remove('hidden'); + if (val && typeof val === 'object' && val.error) { + if (el) el.textContent = def; + if (errorEl) errorEl.textContent = val.error; + } else if (val != null && val !== '') { + if (el) el.textContent = val; + } else { + if (el) el.textContent = def; + } + } + + function updateRdns(listEl, data, loaderEl = null, errorEl = null) { + if (loaderEl) loaderEl.classList.add('hidden'); + if (listEl) listEl.innerHTML = ''; + if (errorEl) errorEl.textContent = ''; + const container = listEl?.closest('div:not(.loader)'); + if (container?.classList.contains('hidden')) container.classList.remove('hidden'); + if (Array.isArray(data)) { + if (data.length > 0) data.forEach(h => { const li = document.createElement('li'); li.textContent = h; listEl.appendChild(li); }); + else if (listEl) listEl.innerHTML = '
  • No rDNS records found.
  • '; + } else if (data?.error) { + if (listEl) listEl.innerHTML = '
  • -
  • '; + if (errorEl) errorEl.textContent = data.error; + } else { + if (listEl) listEl.innerHTML = '
  • -
  • '; + } + } + + function initOrUpdateMap(mapId, lat, lon, mapElement, loaderEl, msgEl) { + if (!mapElement || !loaderEl || !msgEl) return null; + loaderEl.classList.add('hidden'); + let inst = window[mapId + '_instance']; + if (lat != null && lon != null) { + mapElement.classList.remove('hidden'); + msgEl.classList.add('hidden'); + if (inst) { + inst.setView([lat, lon], 13); + inst.eachLayer(l => { if (l instanceof L.Marker) inst.removeLayer(l); }); + L.marker([lat, lon]).addTo(inst).bindPopup('Approximate Location').openPopup(); + } else { + try { + inst = L.map(mapId).setView([lat, lon], 13); + L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { + attribution: '© OpenStreetMap © CARTO', + subdomains: 'abcd', maxZoom: 19 + }).addTo(inst); + L.marker([lat, lon]).addTo(inst).bindPopup('Approximate Location').openPopup(); + window[mapId + '_instance'] = inst; + } catch (e) { + console.error('Map init failed:', e); + mapElement.classList.add('hidden'); + msgEl.classList.remove('hidden'); + msgEl.textContent = 'Error initializing map.'; + return null; + } + } + setTimeout(() => { if (window[mapId + '_instance']) window[mapId + '_instance'].invalidateSize(); }, 100); + return inst; + } else { + mapElement.classList.add('hidden'); + msgEl.classList.remove('hidden'); + msgEl.textContent = 'Map could not be loaded (missing coordinates).'; + if (inst) { inst.remove(); window[mapId + '_instance'] = null; } + return null; + } + } + + // ── Copy buttons ───────────────────────────────────────────── + setupCopyBtn(copyIpBtn, () => ipAddressSpan.textContent); + setupCopyBtn(copyLookupIpBtn, () => lookupIpEl.textContent); + + // ── Lookup button enable/disable ───────────────────────────── + const syncLookupBtn = () => { lookupBtn.disabled = !lookupInput.value.trim(); }; + lookupInput.addEventListener('input', syncLookupBtn); + + // ── Own IP fetch ───────────────────────────────────────────── + async function fetchIpInfo() { + globalError.classList.add('hidden'); + [ipLoader, geoLoader, asnLoader, rdnsLoader, mapLoader].forEach(l => l?.classList.remove('hidden')); + ipAddressLink.classList.add('hidden'); + copyIpBtn.classList.add('hidden'); + mapEl.classList.add('hidden'); + mapMessage.classList.add('hidden'); + + try { + const r = await fetch(`${API}/ipinfo`); + if (!r.ok) throw new Error(`${r.statusText} (${r.status})`); + const data = await r.json(); + currentIp = data.ip; + + ipAddressSpan.textContent = data.ip; + ipAddressLink.classList.remove('hidden'); + copyIpBtn.classList.remove('hidden'); + ipLoader.classList.add('hidden'); + ipAddressLink.addEventListener('click', e => { + e.preventDefault(); + if (currentIp) window._router.navigate('/whois', { query: currentIp }); + }); + + updateField(document.getElementById('country'), data.geo?.countryName ? `${data.geo.countryName} (${data.geo.country})` : null, null, document.getElementById('geo-error')); + updateField(document.getElementById('region'), data.geo?.region); + updateField(document.getElementById('city'), data.geo?.city); + updateField(document.getElementById('postal'), data.geo?.postalCode); + updateField(document.getElementById('coords'), data.geo?.latitude ? `${data.geo.latitude}, ${data.geo.longitude}` : null); + updateField(document.getElementById('timezone'), data.geo?.timezone, geoLoader); + + const asnNum = (data.asn && !data.asn.error) ? data.asn.number : null; + if (asnNum) { + const asnContainer = document.getElementById('asn-number')?.closest('div:not(.loader)'); + if (asnContainer) asnContainer.classList.remove('hidden'); + document.getElementById('asn-number').innerHTML = + `AS${asnNum}`; + } else { + updateField(document.getElementById('asn-number'), null, null, document.getElementById('asn-error'), data.asn?.error || '-'); + } + updateField(document.getElementById('asn-org'), data.asn?.organization, asnLoader); + updateRdns(document.getElementById('rdns-list'), data.rdns, rdnsLoader, document.getElementById('rdns-error')); + map = initOrUpdateMap('map', data.geo?.latitude, data.geo?.longitude, mapEl, mapLoader, mapMessage); + + } catch (err) { + console.error('Failed to fetch IP info:', err); + showGlobalErr(`Could not load IP information. ${err.message}`); + [ipLoader, geoLoader, asnLoader, rdnsLoader, mapLoader].forEach(l => l?.classList.add('hidden')); + } + } + + // ── Lookup ─────────────────────────────────────────────────── + function resetLookup() { + lookupSection.classList.add('hidden'); + lookupResLoader.classList.add('hidden'); + lookupMapLoader.classList.add('hidden'); + lookupMapEl.classList.add('hidden'); + lookupMapMsg.classList.add('hidden'); + lookupPingRes.classList.add('hidden'); + lookupPingLoader.classList.add('hidden'); + portScanSection.classList.add('hidden'); + portScanOutput.innerHTML = ''; + [lookupIpEl, document.getElementById('lookup-country'), document.getElementById('lookup-region'), + document.getElementById('lookup-city'), document.getElementById('lookup-postal'), + document.getElementById('lookup-coords'), document.getElementById('lookup-timezone'), + document.getElementById('lookup-asn-number'), document.getElementById('lookup-asn-org'), + document.getElementById('lookup-geo-error'), document.getElementById('lookup-asn-error'), + document.getElementById('lookup-rdns-error')].forEach(el => { if (el) el.textContent = ''; }); + document.getElementById('lookup-rdns-list').innerHTML = '
  • -
  • '; + lookupPingOutput.textContent = ''; + lookupPingError.textContent = ''; + [lookupPingBtn, lookupTraceBtn, lookupScanBtn].forEach(b => { if (b) b.disabled = true; }); + currentLookupIp = null; + if (window['lookup-map_instance']) { window['lookup-map_instance'].remove(); window['lookup-map_instance'] = null; } + if (portScanEventSource) { portScanEventSource.close(); portScanEventSource = null; } + } + + async function doLookup(query) { + resetLookup(); + lookupErrorEl.classList.add('hidden'); + globalError.classList.add('hidden'); + + const url = new URL(location.href); + url.searchParams.set('ip', query); + history.replaceState({}, '', url); + + lookupSection.classList.remove('hidden'); + lookupResLoader.classList.remove('hidden'); + lookupMapLoader.classList.remove('hidden'); + + let ipToLookup = query; + if (!isValidIp(query)) { + try { + const r = await fetch(`${API}/dns-lookup?domain=${encodeURIComponent(query)}&type=A`); + const data = await r.json(); + if (r.ok && data.success && data.records?.length) { + ipToLookup = Array.isArray(data.records) ? data.records[0] : data.records; + } else { + const r2 = await fetch(`${API}/dns-lookup?domain=${encodeURIComponent(query)}&type=AAAA`); + const data2 = await r2.json(); + if (r2.ok && data2.success && data2.records?.length) { + ipToLookup = Array.isArray(data2.records) ? data2.records[0] : data2.records; + } else { + throw new Error(data.error || 'No A or AAAA records found.'); + } + } + } catch (err) { + lookupErrorEl.textContent = `Error: Could not resolve domain — ${err.message}`; + lookupErrorEl.classList.remove('hidden'); + resetLookup(); + return; + } + } + + try { + const r = await fetch(`${API}/lookup?targetIp=${encodeURIComponent(ipToLookup)}`); + const data = await r.json(); + if (!r.ok) throw new Error(data.error || `HTTP ${r.status}`); + currentLookupIp = data.ip; + + updateField(lookupIpEl, data.ip); + updateField(document.getElementById('lookup-country'), data.geo?.countryName ? `${data.geo.countryName} (${data.geo.country})` : null, null, document.getElementById('lookup-geo-error')); + updateField(document.getElementById('lookup-region'), data.geo?.region); + updateField(document.getElementById('lookup-city'), data.geo?.city); + updateField(document.getElementById('lookup-postal'), data.geo?.postalCode); + updateField(document.getElementById('lookup-coords'), data.geo?.latitude ? `${data.geo.latitude}, ${data.geo.longitude}` : null); + updateField(document.getElementById('lookup-timezone'), data.geo?.timezone); + + if (data.asn?.number) { + document.getElementById('lookup-asn-number').innerHTML = + `AS${data.asn.number}`; + } else { + updateField(document.getElementById('lookup-asn-number'), data.asn?.number, null, document.getElementById('lookup-asn-error')); + } + updateField(document.getElementById('lookup-asn-org'), data.asn?.organization); + updateRdns(document.getElementById('lookup-rdns-list'), data.rdns, null, document.getElementById('lookup-rdns-error')); + lookupMap = initOrUpdateMap('lookup-map', data.geo?.latitude, data.geo?.longitude, lookupMapEl, lookupMapLoader, lookupMapMsg); + [lookupPingBtn, lookupTraceBtn, lookupScanBtn].forEach(b => { if (b) b.disabled = false; }); + + } catch (err) { + lookupErrorEl.textContent = `Error: Lookup failed — ${err.message}`; + lookupErrorEl.classList.remove('hidden'); + lookupMapMsg.textContent = 'Map could not be loaded.'; + lookupMapMsg.classList.remove('hidden'); + lookupMapEl.classList.add('hidden'); + lookupMapLoader.classList.add('hidden'); + resetLookup(); + } finally { + lookupResLoader.classList.add('hidden'); + } + } + + // ── Ping ───────────────────────────────────────────────────── + async function runPing(ip) { + lookupPingRes.classList.remove('hidden'); + lookupPingLoader.classList.remove('hidden'); + lookupPingOutput.textContent = ''; + lookupPingError.textContent = ''; + try { + const r = await fetch(`${API}/ping?targetIp=${encodeURIComponent(ip)}`); + const data = await r.json(); + if (!r.ok || !data.success) throw new Error(data.error || `HTTP ${r.status}`); + let out = `--- Ping Statistics for ${ip} ---\n`; + if (data.stats) { + out += `Packets: ${data.stats.packets.transmitted} sent, ${data.stats.packets.received} received, ${data.stats.packets.lossPercent}% loss\n`; + if (data.stats.rtt) out += `RTT (ms): min=${data.stats.rtt.min} avg=${data.stats.rtt.avg} max=${data.stats.rtt.max}\n`; + } + out += `\n--- Raw Output ---\n${data.rawOutput || ''}`; + lookupPingOutput.textContent = out; + } catch (err) { + lookupPingError.textContent = `Ping Error: ${err.message}`; + } finally { + lookupPingLoader.classList.add('hidden'); + } + } + + // ── Traceroute ─────────────────────────────────────────────── + function startTraceroute(ip) { + if (eventSource) { eventSource.close(); eventSource = null; } + tracerouteSection.classList.remove('hidden'); + tracerouteOutput.textContent = ''; + tracerouteLoader.classList.remove('hidden'); + tracerouteStopBtn.classList.remove('hidden'); + tracerouteMessage.textContent = `Starting traceroute to ${ip}…`; + globalError.classList.add('hidden'); + + eventSource = new EventSource(`${API}/traceroute?targetIp=${encodeURIComponent(ip)}`); + + eventSource.onopen = () => { tracerouteMessage.textContent = `Traceroute to ${ip} in progress…`; }; + eventSource.onerror = () => { + tracerouteMessage.textContent = eventSource.readyState === EventSource.CLOSED + ? 'Connection closed.' : 'Connection error.'; + tracerouteLoader.classList.add('hidden'); + tracerouteStopBtn.classList.add('hidden'); + eventSource.close(); + }; + eventSource.addEventListener('hop', e => { + try { displayHop(JSON.parse(e.data)); } catch { displayTraceLine(`[Parse error: ${e.data}]`, 'error-line'); } + }); + eventSource.addEventListener('info', e => { + try { displayTraceLine(JSON.parse(e.data).message, 'info-line'); } catch {} + }); + eventSource.addEventListener('error', e => { + try { const d = JSON.parse(e.data); displayTraceLine(d.error, 'error-line'); } catch {} + }); + eventSource.addEventListener('end', e => { + try { + const d = JSON.parse(e.data); + const msg = `Traceroute finished${d.exitCode === 0 ? ' successfully' : ` (exit code ${d.exitCode})`}.`; + displayTraceLine(msg, 'end-line'); + tracerouteMessage.textContent = msg; + } catch {} + tracerouteLoader.classList.add('hidden'); + tracerouteStopBtn.classList.add('hidden'); + eventSource.close(); + }); + } + + function stopTraceroute() { + if (eventSource) { eventSource.close(); eventSource = null; } + tracerouteLoader.classList.add('hidden'); + tracerouteStopBtn.classList.add('hidden'); + tracerouteMessage.textContent = 'Traceroute stopped.'; + displayTraceLine('— Stopped by user —', 'info-line'); + } + + function displayTraceLine(text, cls = '') { + const div = document.createElement('div'); + if (cls) div.classList.add(cls); + div.classList.add('fade-in'); + div.textContent = text; + tracerouteOutput.appendChild(div); + tracerouteOutput.scrollTop = tracerouteOutput.scrollHeight; + } + + function displayHop(hop) { + const div = document.createElement('div'); + div.classList.add('hop-line', 'fade-in'); + const num = document.createElement('span'); num.classList.add('hop-number'); num.textContent = hop.hop || '?'; div.appendChild(num); + if (hop.ip) { + const ip = document.createElement('span'); ip.classList.add('hop-ip'); ip.textContent = hop.ip; div.appendChild(ip); + if (hop.hostname) { const h = document.createElement('span'); h.classList.add('hop-hostname'); h.textContent = ` (${hop.hostname})`; div.appendChild(h); } + } else if (hop.rtt?.every(r => r === '*')) { + const t = document.createElement('span'); t.classList.add('hop-timeout'); t.textContent = '* * *'; div.appendChild(t); + } else { + div.appendChild(document.createTextNode(hop.rawLine || 'Unknown hop')); + } + if (Array.isArray(hop.rtt)) { + hop.rtt.forEach(r => { + const s = document.createElement('span'); + s.classList.add(r === '*' ? 'hop-timeout' : 'hop-rtt'); + s.textContent = r === '*' ? ' *' : ` ${r} ms`; + div.appendChild(s); + }); + } + tracerouteOutput.appendChild(div); + tracerouteOutput.scrollTop = tracerouteOutput.scrollHeight; + } + + // ── Port Scan ──────────────────────────────────────────────── + function startPortScan(ip) { + if (portScanEventSource) { portScanEventSource.close(); portScanEventSource = null; } + portScanSection.classList.remove('hidden'); + portScanOutput.innerHTML = ''; + portScanLoader.classList.remove('hidden'); + portScanStopBtn.classList.remove('hidden'); + portScanMessage.textContent = `Starting port scan for ${ip}…`; + + portScanEventSource = new EventSource(`${API}/port-scan?targetIp=${encodeURIComponent(ip)}`); + portScanEventSource.onopen = () => {}; + portScanEventSource.onerror = () => { + portScanMessage.textContent = 'Connection error during port scan.'; + portScanLoader.classList.add('hidden'); + portScanStopBtn.classList.add('hidden'); + portScanEventSource.close(); + }; + portScanEventSource.addEventListener('info', e => { + try { portScanMessage.textContent = JSON.parse(e.data).message; } catch {} + }); + portScanEventSource.addEventListener('port_status', e => { + try { displayPortResult(JSON.parse(e.data)); } catch {} + }); + portScanEventSource.addEventListener('error', e => { + try { displayPortResult({ error: JSON.parse(e.data).error }); } catch {} + }); + portScanEventSource.addEventListener('end', e => { + try { portScanMessage.textContent = JSON.parse(e.data).message; } catch {} + portScanLoader.classList.add('hidden'); + portScanStopBtn.classList.add('hidden'); + portScanEventSource.close(); + }); + } + + function stopPortScan() { + if (portScanEventSource) { portScanEventSource.close(); portScanEventSource = null; } + portScanLoader.classList.add('hidden'); + portScanStopBtn.classList.add('hidden'); + portScanMessage.textContent = 'Port scan stopped.'; + } + + function displayPortResult(data) { + const div = document.createElement('div'); + div.classList.add('mb-1', 'fade-in'); + if (data.error) { + div.innerHTML = `Error: ${data.error}`; + } else { + const colors = { open: 'text-green-400', closed: 'text-red-400', timeout: 'text-yellow-400' }; + const labels = { open: 'OPEN', closed: 'CLOSED', timeout: 'TIMEOUT (Filtered?)' }; + const col = colors[data.status] || 'text-gray-400'; + const lbl = labels[data.status] || (data.status || '').toUpperCase(); + div.innerHTML = `Port ${data.port} (${data.service}): ${lbl}`; + } + portScanOutput.appendChild(div); + portScanOutput.scrollTop = portScanOutput.scrollHeight; + } + + // ── Event listeners ────────────────────────────────────────── + lookupBtn.addEventListener('click', () => { const q = lookupInput.value.trim(); if (q) doLookup(q); }); + lookupInput.addEventListener('keypress', e => { if (e.key === 'Enter' && !lookupBtn.disabled) { const q = lookupInput.value.trim(); if (q) doLookup(q); } }); + lookupPingBtn.addEventListener('click', () => { if (currentLookupIp) runPing(currentLookupIp); }); + lookupTraceBtn.addEventListener('click', () => { if (currentLookupIp) startTraceroute(currentLookupIp); }); + lookupScanBtn.addEventListener('click', () => { if (currentLookupIp) startPortScan(currentLookupIp); }); + tracerouteStopBtn.addEventListener('click', stopTraceroute); + portScanStopBtn.addEventListener('click', stopPortScan); + + // ── Bootstrap ──────────────────────────────────────────────── + fetchIpInfo(); + + const params = new URLSearchParams(search); + const ipParam = params.get('ip'); + if (ipParam) { lookupInput.value = ipParam; syncLookupBtn(); doLookup(ipParam); } + + // ── Cleanup ────────────────────────────────────────────────── + return () => { + if (eventSource) { eventSource.close(); eventSource = null; } + if (portScanEventSource) { portScanEventSource.close(); portScanEventSource = null; } + if (window['map_instance']) { window['map_instance'].remove(); window['map_instance'] = null; } + if (window['lookup-map_instance']) { window['lookup-map_instance'].remove(); window['lookup-map_instance'] = null; } + }; + } +}; diff --git a/frontend/app/pages/mac.js b/frontend/app/pages/mac.js new file mode 100644 index 0000000..c7f4a52 --- /dev/null +++ b/frontend/app/pages/mac.js @@ -0,0 +1,76 @@ +import { API, showError } from '../shared.js'; + +export const page = { + title: 'MAC Vendor Lookup', + + template: () => ` +
    +

    MAC Address Vendor Lookup

    + +
    +
    + + +
    + + + +
    + + +
    `, + + async init(search) { + const input = document.getElementById('mac-input'); + const btn = document.getElementById('mac-lookup-button'); + const errorEl = document.getElementById('mac-lookup-error'); + const section = document.getElementById('mac-lookup-results-section'); + const queryEl = document.getElementById('mac-lookup-query'); + const loader = document.getElementById('mac-lookup-loader'); + const output = document.getElementById('mac-lookup-output'); + + const syncBtn = () => { btn.disabled = !input.value.trim(); }; + input.addEventListener('input', syncBtn); + + async function doLookup() { + const mac = input.value.trim(); + if (!mac) return; + + showError(errorEl, null); + section.classList.remove('hidden'); + loader.classList.remove('hidden'); + output.textContent = ''; + queryEl.textContent = mac; + + try { + const r = await fetch(`${API}/mac-lookup?mac=${encodeURIComponent(mac)}`); + const data = await r.json(); + if (!r.ok || !data.success) throw new Error(data.error || `HTTP ${r.status}`); + output.textContent = data.vendor || 'No vendor found.'; + } catch (err) { + showError(errorEl, err.message); + output.textContent = ''; + } finally { + loader.classList.add('hidden'); + } + } + + btn.addEventListener('click', doLookup); + input.addEventListener('keypress', e => { if (e.key === 'Enter' && !btn.disabled) doLookup(); }); + + const params = new URLSearchParams(search); + const m = params.get('mac'); + if (m) { input.value = m; syncBtn(); doLookup(); } + } +}; diff --git a/frontend/app/pages/subnet.js b/frontend/app/pages/subnet.js new file mode 100644 index 0000000..5d05016 --- /dev/null +++ b/frontend/app/pages/subnet.js @@ -0,0 +1,205 @@ +export const page = { + title: 'Subnetz Rechner', + + template: () => ` +
    +

    IP Subnetz Rechner

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

    + Beispiel-Subnetze (Private Adressbereiche) +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BereichCIDRSubnetzmaskeBeschreibungAktion
    192.168.0.0 – 192.168.255.255/16 (Gesamt)255.255.0.0Klasse C (oft als /24 genutzt)Beispiel /24
    172.16.0.0 – 172.31.255.255/12 (Gesamt)255.240.0.0Klasse BBeispiel /16
    10.0.0.0 – 10.255.255.255/8 (Gesamt)255.0.0.0Klasse ABeispiel /8
    +
    +

    Klicken Sie auf "Beispiel", um die Felder auszufüllen und die Berechnung zu starten.

    +
    +
    `, + + init() { + const form = document.getElementById('subnet-form'); + const ipInput = document.getElementById('ip-address'); + const cidrInput = document.getElementById('cidr'); + const errorEl = document.getElementById('subnet-error'); + const resultsEl = document.getElementById('results'); + + function showInlineError(msg) { + errorEl.textContent = msg; + errorEl.classList.toggle('hidden', !msg); + } + + function isValidIP(ip) { + return /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/.test(ip); + } + + function ipToBinary(ip) { + return ip.split('.').map(o => parseInt(o, 10).toString(2).padStart(8, '0')).join(''); + } + + function binaryToIp(b) { + const parts = []; + for (let i = 0; i < 32; i += 8) parts.push(parseInt(b.slice(i, i + 8), 2)); + return parts.join('.'); + } + + function cidrToMask(cidr) { + return binaryToIp('1'.repeat(cidr) + '0'.repeat(32 - cidr)); + } + + function maskToCidr(mask) { + const b = ipToBinary(mask); + if (/^1*0*$/.test(b)) return b.replace(/0+$/, '').length; + return null; + } + + function calculate() { + showInlineError(null); + const ip = ipInput.value.trim(); + const cidrRaw = cidrInput.value.trim(); + + if (!isValidIP(ip)) { showInlineError('Bitte eine gültige IPv4-Adresse eingeben.'); return; } + + let cidr, mask; + if (cidrRaw.includes('.')) { + if (!isValidIP(cidrRaw)) { showInlineError('Bitte eine gültige Subnetzmaske eingeben.'); return; } + cidr = maskToCidr(cidrRaw); + if (cidr === null) { showInlineError('Ungültige Subnetzmaske — muss eine kontinuierliche Folge von Einsen sein (z.B. 255.255.255.0).'); return; } + mask = cidrRaw; + } else { + cidr = parseInt(cidrRaw, 10); + if (isNaN(cidr) || cidr < 0 || cidr > 32) { showInlineError('Bitte einen gültigen CIDR-Wert (0–32) eingeben.'); return; } + mask = cidrToMask(cidr); + } + + const ipBin = ipToBinary(ip); + const maskBin = '1'.repeat(cidr) + '0'.repeat(32 - cidr); + let netBin = ''; + for (let i = 0; i < 32; i++) netBin += (parseInt(ipBin[i]) & parseInt(maskBin[i])).toString(); + + const hostBits = 32 - cidr; + const bcBin = netBin.slice(0, cidr) + '1'.repeat(hostBits); + const netNum = parseInt(netBin, 2); + const bcNum = parseInt(bcBin, 2); + + let hosts, first, last; + if (hostBits >= 2) { + hosts = Math.pow(2, hostBits) - 2; + first = binaryToIp((netNum + 1).toString(2).padStart(32, '0')); + last = binaryToIp((bcNum - 1).toString(2).padStart(32, '0')); + } else if (cidr === 31) { + hosts = 2; first = binaryToIp(netBin); last = binaryToIp(bcBin); + } else { + hosts = 1; first = binaryToIp(netBin); last = binaryToIp(netBin); + } + + document.getElementById('network-address').textContent = binaryToIp(netBin); + document.getElementById('broadcast-address').textContent = binaryToIp(bcBin); + document.getElementById('subnet-mask').textContent = mask; + document.getElementById('host-count').textContent = hosts.toLocaleString(); + document.getElementById('first-host').textContent = first; + document.getElementById('last-host').textContent = last; + + resultsEl.classList.remove('hidden'); + } + + form.addEventListener('submit', e => { e.preventDefault(); calculate(); }); + + document.querySelectorAll('.example-link').forEach(link => { + link.addEventListener('click', () => { + ipInput.value = link.dataset.ip; + cidrInput.value = link.dataset.cidr; + calculate(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }); + }); + } +}; diff --git a/frontend/app/pages/whois.js b/frontend/app/pages/whois.js new file mode 100644 index 0000000..c39165c --- /dev/null +++ b/frontend/app/pages/whois.js @@ -0,0 +1,88 @@ +import { API, setupCopyBtn, showError } from '../shared.js'; + +export const page = { + title: 'WHOIS Lookup', + + template: () => ` +
    +

    WHOIS Lookup

    + +
    +
    + + +
    + + + +
    + + +
    `, + + async init(search) { + const input = document.getElementById('whois-query-input'); + const btn = document.getElementById('whois-lookup-button'); + const errorEl = document.getElementById('whois-lookup-error'); + const section = document.getElementById('whois-lookup-results-section'); + const queryEl = document.getElementById('whois-lookup-query'); + const loader = document.getElementById('whois-lookup-loader'); + const output = document.getElementById('whois-lookup-output'); + const copyBtn = document.getElementById('copy-whois-btn'); + + const syncBtn = () => { btn.disabled = !input.value.trim(); }; + input.addEventListener('input', syncBtn); + + setupCopyBtn(copyBtn, () => output.textContent); + + async function doLookup() { + const query = input.value.trim(); + if (!query) return; + + const url = new URL(location.href); + url.searchParams.set('query', query); + history.replaceState({}, '', url); + + showError(errorEl, null); + section.classList.remove('hidden'); + loader.classList.remove('hidden'); + output.textContent = ''; + queryEl.textContent = query; + + try { + const r = await fetch(`${API}/whois-lookup?query=${encodeURIComponent(query)}`); + const data = await r.json(); + if (!r.ok || !data.success) throw new Error(data.error || `HTTP ${r.status}`); + output.textContent = typeof data.result === 'string' + ? data.result + : JSON.stringify(data.result, null, 2); + } catch (err) { + showError(errorEl, err.message); + output.textContent = ''; + } finally { + loader.classList.add('hidden'); + } + } + + btn.addEventListener('click', doLookup); + input.addEventListener('keypress', e => { if (e.key === 'Enter' && !btn.disabled) doLookup(); }); + + const params = new URLSearchParams(search); + const q = params.get('query'); + if (q) { input.value = q; syncBtn(); doLookup(); } + } +}; diff --git a/frontend/app/router.js b/frontend/app/router.js new file mode 100644 index 0000000..15ef02d --- /dev/null +++ b/frontend/app/router.js @@ -0,0 +1,86 @@ +import { page as homePage } from './pages/home.js'; +import { page as subnetPage } from './pages/subnet.js'; +import { page as dnsPage } from './pages/dns.js'; +import { page as whoisPage } from './pages/whois.js'; +import { page as macPage } from './pages/mac.js'; +import { page as asnPage } from './pages/asn.js'; + +const routes = { + '/': homePage, + '/subnet': subnetPage, + '/dns': dnsPage, + '/whois': whoisPage, + '/mac': macPage, + '/asn': asnPage, +}; + +const app = document.getElementById('app'); +let currentCleanup = null; + +function setActiveNav(path) { + document.querySelectorAll('nav a').forEach(a => { + try { + const p = new URL(a.href).pathname; + a.classList.toggle('active-link', p === path); + } catch {} + }); +} + +async function navigate(path, { push = true, search = '' } = {}) { + const route = routes[path] ?? routes['/']; + + // ── leave animation ────────────────────────────────────────── + app.classList.add('page-leaving'); + if (currentCleanup) { try { currentCleanup(); } catch {} currentCleanup = null; } + await new Promise(r => setTimeout(r, 200)); + + // ── swap content ───────────────────────────────────────────── + app.innerHTML = route.template(); + document.title = route.title ? `${route.title} – uTools` : 'uTools – Network Suite'; + setActiveNav(path); + + const fullUrl = path + (search ? (search.startsWith('?') ? search : '?' + search) : ''); + if (push) history.pushState({ path }, '', fullUrl); + + // ── enter animation ────────────────────────────────────────── + app.classList.remove('page-leaving'); + app.classList.add('page-entering'); + setTimeout(() => app.classList.remove('page-entering'), 300); + + // ── init page ──────────────────────────────────────────────── + const cleanup = await route.init(search); + currentCleanup = typeof cleanup === 'function' ? cleanup : null; +} + +// ── Intercept same-origin link clicks ─────────────────────────── +document.addEventListener('click', e => { + const a = e.target.closest('a[href]'); + if (!a) return; + let url; + try { url = new URL(a.href); } catch { return; } + if (url.origin !== location.origin) return; + if (!(url.pathname in routes)) return; + e.preventDefault(); + navigate(url.pathname, { push: true, search: url.search }); +}); + +window.addEventListener('popstate', () => { + navigate(location.pathname, { push: false, search: location.search }); +}); + +// ── Expose for programmatic navigation ────────────────────────── +window._router = { + navigate(path, searchObj = {}) { + const s = new URLSearchParams(searchObj).toString(); + navigate(path, { push: true, search: s ? '?' + s : '' }); + } +}; + +// ── Fetch version once ─────────────────────────────────────────── +fetch('/api/version') + .then(r => r.json()) + .then(d => { const el = document.getElementById('commit-sha'); if (el) el.textContent = d.commitSha || 'unknown'; }) + .catch(() => { const el = document.getElementById('commit-sha'); if (el) el.textContent = 'error'; }); + +// ── Initial render ─────────────────────────────────────────────── +navigate(location.pathname, { push: false, search: location.search }); diff --git a/frontend/app/script.js b/frontend/app/script.js deleted file mode 100644 index 133e037..0000000 --- a/frontend/app/script.js +++ /dev/null @@ -1,906 +0,0 @@ -// script.js - Hauptlogik für index.html (IP Info, IP Lookup, Traceroute) -document.addEventListener('DOMContentLoaded', () => { - // --- DOM Elements (User IP Info) --- - const ipAddressLinkEl = document.getElementById('ip-address-link'); // Geändert von ip-address - const ipAddressSpanEl = document.getElementById('ip-address'); // Das Span *innerhalb* des Links - const countryEl = document.getElementById('country'); - const regionEl = document.getElementById('region'); - const cityEl = document.getElementById('city'); - const postalEl = document.getElementById('postal'); - const coordsEl = document.getElementById('coords'); - const timezoneEl = document.getElementById('timezone'); - const asnNumberEl = document.getElementById('asn-number'); - const asnOrgEl = document.getElementById('asn-org'); - const rdnsListEl = document.getElementById('rdns-list'); - const mapContainer = document.getElementById('map-container'); - const mapEl = document.getElementById('map'); - const mapMessageEl = document.getElementById('map-message'); - const globalErrorEl = document.getElementById('global-error'); - const ipLoader = document.getElementById('ip-loader'); - const geoLoader = document.getElementById('geo-loader'); - const asnLoader = document.getElementById('asn-loader'); - const rdnsLoader = document.getElementById('rdns-loader'); - const mapLoader = document.getElementById('map-loader'); - const geoErrorEl = document.getElementById('geo-error'); - const asnErrorEl = document.getElementById('asn-error'); - const rdnsErrorEl = document.getElementById('rdns-error'); - const geoInfo = document.getElementById('geo-info'); - const asnInfo = document.getElementById('asn-info'); - const rdnsInfo = document.getElementById('rdns-info'); - - - // --- DOM Elements (Lookup) --- - const lookupIpInput = document.getElementById('lookup-ip-input'); - const lookupButton = document.getElementById('lookup-button'); - const lookupErrorEl = document.getElementById('lookup-error'); - const lookupStatusEl = document.getElementById('lookup-status'); // Optional: für Statusmeldungen wie "Resolving..." - const lookupResultsSection = document.getElementById('lookup-results-section'); - const lookupIpAddressEl = document.getElementById('lookup-ip-address'); - const lookupResultLoader = document.getElementById('lookup-result-loader'); - const lookupCountryEl = document.getElementById('lookup-country'); - const lookupRegionEl = document.getElementById('lookup-region'); - const lookupCityEl = document.getElementById('lookup-city'); - const lookupPostalEl = document.getElementById('lookup-postal'); - const lookupCoordsEl = document.getElementById('lookup-coords'); - const lookupTimezoneEl = document.getElementById('lookup-timezone'); - const lookupGeoErrorEl = document.getElementById('lookup-geo-error'); - const lookupAsnNumberEl = document.getElementById('lookup-asn-number'); - const lookupAsnOrgEl = document.getElementById('lookup-asn-org'); - const lookupAsnErrorEl = document.getElementById('lookup-asn-error'); - const lookupRdnsListEl = document.getElementById('lookup-rdns-list'); - const lookupRdnsErrorEl = document.getElementById('lookup-rdns-error'); - const lookupMapContainer = document.getElementById('lookup-map-container'); - const lookupMapEl = document.getElementById('lookup-map'); - const lookupMapLoader = document.getElementById('lookup-map-loader'); - const lookupMapMessageEl = document.getElementById('lookup-map-message'); - const lookupPingButton = document.getElementById('lookup-ping-button'); - const lookupTraceButton = document.getElementById('lookup-trace-button'); - const lookupPingResultsEl = document.getElementById('lookup-ping-results'); - const lookupPingLoader = document.getElementById('lookup-ping-loader'); - const lookupPingOutputEl = document.getElementById('lookup-ping-output'); - const lookupPingErrorEl = document.getElementById('lookup-ping-error'); - - - // --- DOM Elements (Traceroute) --- - const tracerouteSection = document.getElementById('traceroute-section'); - const tracerouteOutputEl = document.querySelector('#traceroute-output pre'); - const tracerouteLoader = document.getElementById('traceroute-loader'); - const tracerouteMessage = document.getElementById('traceroute-message'); - - // --- DOM Elements (Port Scan) --- - const portScanSection = document.getElementById('port-scan-section'); - const portScanOutputEl = document.getElementById('port-scan-output'); - const portScanLoader = document.getElementById('port-scan-loader'); - const portScanMessage = document.getElementById('port-scan-message'); - const lookupScanButton = document.getElementById('lookup-scan-button'); - - // --- DOM Elements (Footer) --- - const commitShaEl = document.getElementById('commit-sha'); - - // --- Configuration --- - const API_BASE_URL = '/api'; // Anpassen, falls nötig - - // --- State --- - let map = null; // Leaflet map instance for user's IP - let lookupMap = null; // Leaflet map instance for lookup results - let currentIp = null; // Store the user's fetched IP - let currentLookupIp = null; // Store the last successfully looked-up IP - let eventSource = null; // Store the EventSource instance for traceroute - let portScanEventSource = null; // Store the EventSource for port scan - - // --- Helper Functions --- - - /** Zeigt globale Fehler an */ - function showGlobalError(message) { - if (!globalErrorEl) return; - globalErrorEl.textContent = `Error: ${message}`; - globalErrorEl.classList.remove('hidden'); - } - - /** Versteckt globale Fehler */ - function hideGlobalError() { - if (!globalErrorEl) return; - globalErrorEl.classList.add('hidden'); - } - - /** - * Prüft, ob der String eine gültige IPv4 oder IPv6 Adresse ist. - * @param {string} input - Der zu prüfende String. - * @returns {boolean} True, wenn es eine gültige IP ist, sonst false. - */ - function isValidIpAddress(input) { - if (!input || typeof input !== 'string') return false; - const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - // Sehr einfache IPv6 Regex (erkennt gültige Zeichen, aber nicht alle komplexen Formate perfekt) - const ipv6Regex = /^[a-fA-F0-9:]+$/; - // Komplexere IPv6 Regex (versucht mehr Fälle abzudecken, aber immer noch nicht perfekt) - const complexIpv6Regex = /^(?:(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,7}:|(?:[a-fA-F0-9]{1,4}:){1,6}:[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,5}(?::[a-fA-F0-9]{1,4}){1,2}|(?:[a-fA-F0-9]{1,4}:){1,4}(?::[a-fA-F0-9]{1,4}){1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}(?::[a-fA-F0-9]{1,4}){1,4}|(?:[a-fA-F0-9]{1,4}:){1,2}(?::[a-fA-F0-9]{1,4}){1,5}|[a-fA-F0-9]{1,4}:(?:(?::[a-fA-F0-9]{1,4}){1,6})|:(?:(?::[a-fA-F0-9]{1,4}){1,7}|:)|fe80:(?::[a-fA-F0-9]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[a-fA-F0-9]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; - - return ipv4Regex.test(input) || complexIpv6Regex.test(input); - } - - - /** - * Aktualisiert ein Info-Feld und versteckt optional einen Loader. - * @param {HTMLElement} valueElement - Das Element, das den Wert anzeigt. - * @param {any} value - Der anzuzeigende Wert oder ein Fehlerobjekt {error: string}. - * @param {HTMLElement} [loaderElement] - Das zu versteckende Loader-Element. - * @param {HTMLElement} [errorElement] - Das Element zur Anzeige von Fehlern für dieses Feld. - * @param {string} [defaultValue='-'] - Standardwert bei fehlenden Daten. - */ - function updateField(valueElement, value, loaderElement = null, errorElement = null, defaultValue = '-') { - if (loaderElement) loaderElement.classList.add('hidden'); - if (errorElement) errorElement.textContent = ''; // Clear previous error - - // Zeige das Elternelement des valueElements, falls es vorher versteckt war (für initiale Ladeanzeige) - const dataContainer = valueElement?.closest('div:not(.loader)'); // Find closest parent div that isn't a loader - if (dataContainer?.classList.contains('hidden')) { - dataContainer.classList.remove('hidden'); - } - - if (value && typeof value === 'object' && value.error) { - if (valueElement) valueElement.textContent = defaultValue; - if (errorElement) errorElement.textContent = value.error; - else console.warn(`Error in field ${valueElement?.id}: ${value.error}`); - } else if (value !== null && value !== undefined && value !== '') { - if (valueElement) valueElement.textContent = value; - } else { - if (valueElement) valueElement.textContent = defaultValue; - } - } - - /** - * Aktualisiert die rDNS Liste generisch. - * @param {HTMLElement} listElement - Das UL Element. - * @param {Array|object} rdnsData - Die rDNS Daten oder ein Fehlerobjekt. - * @param {HTMLElement} [loaderElement] - Das zu versteckende Loader-Element. - * @param {HTMLElement} [errorElement] - Das Element zur Anzeige von Fehlern. - */ - function updateRdns(listElement, rdnsData, loaderElement = null, errorElement = null) { - if (loaderElement) loaderElement.classList.add('hidden'); - if (listElement) listElement.innerHTML = ''; // Clear previous entries - if (errorElement) errorElement.textContent = ''; - - // Zeige das Elternelement des listElements, falls es vorher versteckt war - const dataContainer = listElement?.closest('div:not(.loader)'); - if (dataContainer?.classList.contains('hidden')) { - dataContainer.classList.remove('hidden'); - } - - if (rdnsData && Array.isArray(rdnsData)) { - if (rdnsData.length > 0) { - rdnsData.forEach(hostname => { - const li = document.createElement('li'); - li.textContent = hostname; - if (listElement) listElement.appendChild(li); - }); - } else { - if (listElement) listElement.innerHTML = '
  • No rDNS records found.
  • '; // Klarere Meldung - } - } else if (rdnsData && rdnsData.error) { - if (listElement) listElement.innerHTML = '
  • -
  • '; - if (errorElement) errorElement.textContent = rdnsData.error; - } else { - if (listElement) listElement.innerHTML = '
  • -
  • '; - } - } - - /** - * Initialisiert oder aktualisiert eine Leaflet-Karte. - * @param {string} mapId - Die ID des Map-Containers ('map' oder 'lookup-map'). - * @param {number|null} lat - Breitengrad. - * @param {number|null} lon - Längengrad. - * @param {HTMLElement} mapElement - Das Karten-Div. - * @param {HTMLElement} loaderElement - Das Loader-Element für die Karte. - * @param {HTMLElement} messageElement - Das Nachrichten-Element für die Karte. - * @returns {L.Map | null} Die Karteninstanz oder null bei Fehler. - */ - function initOrUpdateMap(mapId, lat, lon, mapElement, loaderElement, messageElement) { - if (!mapElement || !loaderElement || !messageElement) return null; // Exit if elements are missing - loaderElement.classList.add('hidden'); // Hide loader first - - // Use a unique variable name for the map instance based on mapId - let mapInstance = window[mapId + '_instance']; - - if (lat != null && lon != null) { // Check for non-null coordinates - mapElement.classList.remove('hidden'); - messageElement.classList.add('hidden'); - - if (mapInstance) { - mapInstance.setView([lat, lon], 13); - mapInstance.eachLayer((layer) => { - if (layer instanceof L.Marker) mapInstance.removeLayer(layer); - }); - L.marker([lat, lon]).addTo(mapInstance).bindPopup(`Approximate Location`).openPopup(); - } else { - try { - mapInstance = L.map(mapId).setView([lat, lon], 13); - L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { - attribution: '© OpenStreetMap contributors © CARTO', - subdomains: 'abcd', - maxZoom: 19 - }).addTo(mapInstance); - L.marker([lat, lon]).addTo(mapInstance).bindPopup(`Approximate Location`).openPopup(); - window[mapId + '_instance'] = mapInstance; // Store instance - } catch (e) { - console.error(`Leaflet map initialization failed for ${mapId}:`, e); - mapElement.classList.add('hidden'); - messageElement.classList.remove('hidden'); - messageElement.textContent = 'Error initializing map.'; - return null; - } - } - // Invalidate size after showing/updating to prevent grey tiles - setTimeout(() => { - if (window[mapId + '_instance']) { // Check if map still exists - window[mapId + '_instance'].invalidateSize(); - } - }, 100); - return mapInstance; - } else { - mapElement.classList.add('hidden'); - messageElement.classList.remove('hidden'); - messageElement.textContent = 'Map could not be loaded (missing or invalid coordinates).'; - // If map existed, remove it to clean up resources - if (mapInstance) { - mapInstance.remove(); - window[mapId + '_instance'] = null; - } - return null; - } - } - - /** Ruft die IP-Informationen für die eigene IP ab */ - async function fetchIpInfo() { - hideGlobalError(); - [ipLoader, geoLoader, asnLoader, rdnsLoader, mapLoader].forEach(l => l?.classList.remove('hidden')); - // Hide data elements initially (containers are hidden by default in HTML) - if (ipAddressLinkEl) ipAddressLinkEl.classList.add('hidden'); // Hide link initially - if (mapEl) mapEl.classList.add('hidden'); - // Ensure map message is hidden initially - if (mapMessageEl) mapMessageEl.classList.add('hidden'); - - - try { - const response = await fetch(`${API_BASE_URL}/ipinfo`); - if (!response.ok) throw new Error(`Network response: ${response.statusText} (${response.status})`); - const data = await response.json(); - console.log('Received User IP Info:', data); - - currentIp = data.ip; - // Update the span inside the link - updateField(ipAddressSpanEl, data.ip, ipLoader); - if (ipAddressLinkEl) { - ipAddressLinkEl.classList.remove('hidden'); // Show link element - if (data.ip) { - // Remove old listener if it exists (safety) - ipAddressLinkEl.removeEventListener('click', handleIpClick); - // Add new listener - ipAddressLinkEl.addEventListener('click', handleIpClick); - } - } - - updateField(countryEl, data.geo?.countryName ? `${data.geo.countryName} (${data.geo.country})` : null, null, geoErrorEl); - updateField(regionEl, data.geo?.region); - updateField(cityEl, data.geo?.city); - updateField(postalEl, data.geo?.postalCode); - updateField(coordsEl, data.geo?.latitude ? `${data.geo.latitude}, ${data.geo.longitude}` : null); - updateField(timezoneEl, data.geo?.timezone, geoLoader); // Hide loader on last geo field - - // ASN — render as clickable link if has a number (not an error object) - const asnNum = (data.asn && !data.asn.error) ? data.asn.number : null; - if (asnNum && asnNumberEl) { - // Reveal the hidden data container manually (updateField won't run the link path via error branch) - const asnContainer = asnNumberEl.closest('div:not(.loader)'); - if (asnContainer) asnContainer.classList.remove('hidden'); - asnNumberEl.innerHTML = - `AS${asnNum}`; - } else { - updateField(asnNumberEl, null, null, asnErrorEl, data.asn?.error || '-'); - } - updateField(asnOrgEl, data.asn?.organization, asnLoader); - - updateRdns(rdnsListEl, data.rdns, rdnsLoader, rdnsErrorEl); - - map = initOrUpdateMap('map', data.geo?.latitude, data.geo?.longitude, mapEl, mapLoader, mapMessageEl); - - } catch (error) { - console.error('Failed to fetch user IP info:', error); - showGlobalError(`Could not load initial IP information. ${error.message}`); - [ipLoader, geoLoader, asnLoader, rdnsLoader, mapLoader].forEach(l => l?.classList.add('hidden')); - // Ensure data containers are visible to show potential errors inside them - [geoInfo, asnInfo, rdnsInfo].forEach(container => { - const dataDiv = container?.querySelector('div:not(.loader)'); // Select the data div, not the loader - if (dataDiv) dataDiv.classList.remove('hidden'); - }); - if (mapMessageEl) { - mapMessageEl.textContent = 'Map could not be loaded due to an error.'; - mapMessageEl.classList.remove('hidden'); - } - } - } - - /** Ruft die Versionsinformationen (Commit SHA) ab */ - async function fetchVersionInfo() { - if (!commitShaEl) return; // Don't fetch if element doesn't exist - try { - const response = await fetch(`${API_BASE_URL}/version`); - if (!response.ok) throw new Error(`Network response: ${response.statusText} (${response.status})`); - const data = await response.json(); - commitShaEl.textContent = data.commitSha || 'unknown'; - } catch (error) { - console.error('Failed to fetch version info:', error); - commitShaEl.textContent = 'error'; - // Optionally show global error - // showGlobalError(`Could not load version info: ${error.message}`); - } - } - - // --- Lookup Functions --- - - // --- URL Parameter Functions --- - - /** - * Updates the URL with IP lookup parameter - * @param {string} ip - The IP address or domain to lookup - */ - function updateLookupUrlParams(ip) { - const url = new URL(window.location); - url.searchParams.set('ip', ip); - window.history.pushState({}, '', url); - } - - /** - * Reads URL parameters and returns IP if present - * @returns {string|null} IP or domain from URL, or null if not present - */ - function getLookupUrlParams() { - const urlParams = new URLSearchParams(window.location.search); - return urlParams.get('ip'); - } - - - /** Zeigt Fehler im Lookup-Bereich an */ - function showLookupError(message) { - if (!lookupErrorEl) return; - lookupErrorEl.textContent = `Error: ${message}`; - lookupErrorEl.classList.remove('hidden'); - // Hide status message if error occurs - if (lookupStatusEl) lookupStatusEl.classList.add('hidden'); - } - - /** Versteckt Fehler im Lookup-Bereich */ - function hideLookupError() { - if (!lookupErrorEl) return; - lookupErrorEl.classList.add('hidden'); - } - - /** Zeigt eine Statusmeldung im Lookup-Bereich an */ - function showLookupStatus(message) { - if (!lookupStatusEl) return; - lookupStatusEl.textContent = message; - lookupStatusEl.classList.remove('hidden'); - hideLookupError(); // Hide errors when showing status - } - - /** Versteckt die Statusmeldung im Lookup-Bereich */ - function hideLookupStatus() { - if (!lookupStatusEl) return; - lookupStatusEl.classList.add('hidden'); - } - - - /** Setzt den Lookup-Ergebnisbereich zurück */ - function resetLookupResults() { - if (!lookupResultsSection) return; - lookupResultsSection.classList.add('hidden'); - if (lookupResultLoader) lookupResultLoader.classList.add('hidden'); - if (lookupMapLoader) lookupMapLoader.classList.add('hidden'); - if (lookupMapEl) lookupMapEl.classList.add('hidden'); - if (lookupMapMessageEl) lookupMapMessageEl.classList.add('hidden'); - if (lookupPingResultsEl) lookupPingResultsEl.classList.add('hidden'); // Hide ping results too - if (lookupPingLoader) lookupPingLoader.classList.add('hidden'); - if (lookupPingOutputEl) lookupPingOutputEl.textContent = ''; - if (lookupPingErrorEl) lookupPingErrorEl.textContent = ''; - if (portScanSection) portScanSection.classList.add('hidden'); // Hide port scan results - if (portScanOutputEl) portScanOutputEl.innerHTML = ''; - hideLookupStatus(); // Hide status on reset - - const fieldsToClear = [ - lookupIpAddressEl, lookupCountryEl, lookupRegionEl, lookupCityEl, - lookupPostalEl, lookupCoordsEl, lookupTimezoneEl, lookupAsnNumberEl, - lookupAsnOrgEl, lookupGeoErrorEl, lookupAsnErrorEl, lookupRdnsErrorEl - ]; - fieldsToClear.forEach(el => { if (el) el.textContent = ''; }); - if (lookupRdnsListEl) lookupRdnsListEl.innerHTML = '
  • -
  • '; - - if (lookupPingButton) lookupPingButton.disabled = true; - if (lookupTraceButton) lookupTraceButton.disabled = true; - if (lookupScanButton) lookupScanButton.disabled = true; - currentLookupIp = null; - - // Remove lookup map instance if it exists - if (window['lookup-map_instance']) { - window['lookup-map_instance'].remove(); - window['lookup-map_instance'] = null; - } - - if (portScanEventSource) { - portScanEventSource.close(); - portScanEventSource = null; - } - } - - /** Ruft Informationen für eine spezifische IP ab */ - async function fetchLookupInfo(ipToLookup) { - resetLookupResults(); // Reset before showing new results/loaders - hideLookupError(); - hideGlobalError(); - if (!lookupResultsSection || !lookupResultLoader || !lookupMapLoader) return; // Exit if elements missing - - lookupResultsSection.classList.remove('hidden'); - lookupResultLoader.classList.remove('hidden'); - lookupMapLoader.classList.remove('hidden'); // Show map loader initially - hideLookupStatus(); // Hide status like "Resolving..." - - try { - const response = await fetch(`${API_BASE_URL}/lookup?targetIp=${encodeURIComponent(ipToLookup)}`); // Use targetIp parameter - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || `Network response: ${response.statusText} (${response.status})`); - } - - console.log('Received Lookup Info for', ipToLookup, ':', data); - currentLookupIp = data.ip; // Store the IP that was actually looked up - - updateField(lookupIpAddressEl, data.ip); // Display the looked-up IP - updateField(lookupCountryEl, data.geo?.countryName ? `${data.geo.countryName} (${data.geo.country})` : null, null, lookupGeoErrorEl); - updateField(lookupRegionEl, data.geo?.region); - updateField(lookupCityEl, data.geo?.city); - updateField(lookupPostalEl, data.geo?.postalCode); - updateField(lookupCoordsEl, data.geo?.latitude ? `${data.geo.latitude}, ${data.geo.longitude}` : null); - updateField(lookupTimezoneEl, data.geo?.timezone); - - // ASN — render as clickable link if available - if (data.asn?.number && lookupAsnNumberEl) { - lookupAsnNumberEl.innerHTML = - `AS${data.asn.number}`; - } else { - updateField(lookupAsnNumberEl, data.asn?.number, null, lookupAsnErrorEl); - } - updateField(lookupAsnOrgEl, data.asn?.organization); - - updateRdns(lookupRdnsListEl, data.rdns, null, lookupRdnsErrorEl); - - lookupMap = initOrUpdateMap('lookup-map', data.geo?.latitude, data.geo?.longitude, lookupMapEl, lookupMapLoader, lookupMapMessageEl); - - if (lookupPingButton) lookupPingButton.disabled = false; - if (lookupTraceButton) lookupTraceButton.disabled = false; - if (lookupScanButton) lookupScanButton.disabled = false; - - } catch (error) { - console.error('Failed to fetch lookup info for', ipToLookup, ':', error); - showLookupError(`Lookup failed: ${error.message}`); - if (lookupMapMessageEl) { - lookupMapMessageEl.textContent = 'Map could not be loaded due to an error.'; - lookupMapMessageEl.classList.remove('hidden'); - } - if (lookupMapEl) lookupMapEl.classList.add('hidden'); - if (lookupMapLoader) lookupMapLoader.classList.add('hidden'); // Hide loader on error - resetLookupResults(); // Hide the section again on error - - } finally { - if (lookupResultLoader) lookupResultLoader.classList.add('hidden'); // Hide main loader - // Map loader is handled by initOrUpdateMap - } - } - - /** - * Löst einen Domainnamen zu einer IP-Adresse auf (bevorzugt IPv4). - * @param {string} domain - Der aufzulösende Domainname. - * @returns {Promise} Eine Promise, die mit der IP-Adresse oder null aufgelöst wird. - */ - async function resolveDomainToIp(domain) { - console.log(`Attempting to resolve domain: ${domain}`); - try { - // 1. Versuche A Record (IPv4) - let response = await fetch(`${API_BASE_URL}/dns-lookup?domain=${encodeURIComponent(domain)}&type=A`); - let data = await response.json(); - - if (response.ok && data.success && data.records && Array.isArray(data.records) && data.records.length > 0) { - console.log(`Resolved ${domain} to IPv4: ${data.records[0]}`); - return data.records[0]; // Nimm die erste IPv4-Adresse - } - - // 2. Wenn kein A-Record, versuche AAAA Record (IPv6) - console.log(`No A record found for ${domain}, trying AAAA.`); - response = await fetch(`${API_BASE_URL}/dns-lookup?domain=${encodeURIComponent(domain)}&type=AAAA`); - data = await response.json(); - - if (response.ok && data.success && data.records && Array.isArray(data.records) && data.records.length > 0) { - console.log(`Resolved ${domain} to IPv6: ${data.records[0]}`); - return data.records[0]; // Nimm die erste IPv6-Adresse - } - - // 3. Wenn beides fehlschlägt oder keine Records gefunden wurden - console.warn(`Could not resolve domain ${domain} to an IP address.`); - throw new Error(data.error || 'No A or AAAA records found.'); - - } catch (error) { - console.error('DNS resolution failed for', domain, ':', error); - throw new Error(`Could not resolve domain: ${error.message}`); - } - } - - - // --- Ping Function (for Lookup) --- - async function runLookupPing(ip) { - if (!ip || !lookupPingResultsEl || !lookupPingLoader || !lookupPingOutputEl || !lookupPingErrorEl) return; - - lookupPingResultsEl.classList.remove('hidden'); - lookupPingLoader.classList.remove('hidden'); - lookupPingOutputEl.textContent = ''; - lookupPingErrorEl.textContent = ''; - hideLookupError(); // Hide general lookup errors - - try { - const response = await fetch(`${API_BASE_URL}/ping?targetIp=${encodeURIComponent(ip)}`); - const data = await response.json(); - - if (!response.ok || !data.success) { - throw new Error(data.error || `Ping request failed with status ${response.status}`); - } - - console.log(`Ping results for ${ip}:`, data); - - // Display parsed results nicely - let outputText = `--- Ping Statistics for ${ip} ---\n`; - if (data.stats) { - outputText += `Packets: ${data.stats.packets.transmitted} transmitted, ${data.stats.packets.received} received, ${data.stats.packets.lossPercent}% loss\n`; - if (data.stats.rtt) { - outputText += `Round Trip Time (ms): min=${data.stats.rtt.min}, avg=${data.stats.rtt.avg}, max=${data.stats.rtt.max}, mdev=${data.stats.rtt.mdev}\n`; - } else if (data.stats.packets.received === 0) { - outputText += `Status: Host unreachable or request timed out.\n`; - } - } else { - outputText += `Could not parse statistics.\n`; - } - outputText += `\n--- Raw Output ---\n${data.rawOutput || 'No raw output available.'}`; - lookupPingOutputEl.textContent = outputText; - - } catch (error) { - console.error(`Failed to run ping for ${ip}:`, error); - lookupPingErrorEl.textContent = `Ping Error: ${error.message}`; - } finally { - lookupPingLoader.classList.add('hidden'); - } - } - - - // --- Traceroute Functions --- - function startTraceroute(ip) { - if (!ip) { - showGlobalError('Cannot start traceroute: IP address is missing.'); - return; - } - if (!tracerouteSection || !tracerouteOutputEl || !tracerouteLoader || !tracerouteMessage) return; - - if (eventSource) { - eventSource.close(); - console.log('Previous EventSource closed.'); - } - - tracerouteSection.classList.remove('hidden'); - tracerouteOutputEl.textContent = ''; - tracerouteLoader.classList.remove('hidden'); - tracerouteMessage.textContent = `Starting traceroute to ${ip}...`; - hideGlobalError(); - hideLookupError(); - - const url = `${API_BASE_URL}/traceroute?targetIp=${encodeURIComponent(ip)}`; - eventSource = new EventSource(url); - - eventSource.onopen = () => { - console.log('SSE connection opened for traceroute.'); - tracerouteMessage.textContent = `Traceroute to ${ip} in progress...`; - }; - - eventSource.onerror = (event) => { - console.error('EventSource failed:', event); - let errorMsg = 'Connection error during traceroute.'; - if (eventSource.readyState === EventSource.CLOSED) { - errorMsg = 'Connection closed. Server might have stopped or a network issue occurred.'; - } - tracerouteMessage.textContent = errorMsg; - tracerouteLoader.classList.add('hidden'); - // Don't show global error here, as it might be a normal close - eventSource.close(); - }; - - eventSource.addEventListener('hop', (event) => { - try { - const hopData = JSON.parse(event.data); - displayTracerouteHop(hopData); - } catch (e) { displayTracerouteLine(`[Error parsing hop data: ${event.data}]`, 'error-line'); } - }); - - eventSource.addEventListener('info', (event) => { - try { - const infoData = JSON.parse(event.data); - displayTracerouteLine(infoData.message, 'info-line'); - } catch (e) { displayTracerouteLine(`[Error parsing info data: ${event.data}]`, 'error-line'); } - }); - - eventSource.addEventListener('error', (event) => { // Backend error event - try { - const errorData = JSON.parse(event.data); - displayTracerouteLine(errorData.error, 'error-line'); - tracerouteMessage.textContent = `Error during traceroute: ${errorData.error}`; - } catch (e) { displayTracerouteLine(`[Received unparseable error event: ${event.data}]`, 'error-line'); } - }); - - eventSource.addEventListener('end', (event) => { - console.log('SSE connection closed by server (end event).'); - try { - const endData = JSON.parse(event.data); - const endMessage = `Traceroute finished ${endData.exitCode === 0 ? 'successfully' : `with exit code ${endData.exitCode}`}.`; - displayTracerouteLine(endMessage, 'end-line'); - tracerouteMessage.textContent = endMessage; - } catch (e) { displayTracerouteLine('[Traceroute finished, error parsing end event]', 'end-line'); } - tracerouteLoader.classList.add('hidden'); - eventSource.close(); - }); - } - - function displayTracerouteLine(text, className = '') { - if (!tracerouteOutputEl) return; - const lineDiv = document.createElement('div'); - if (className) lineDiv.classList.add(className); - lineDiv.classList.add('fade-in'); // Animation hinzufügen - lineDiv.textContent = text; - tracerouteOutputEl.appendChild(lineDiv); - tracerouteOutputEl.scrollTop = tracerouteOutputEl.scrollHeight; - } - - function displayTracerouteHop(hopData) { - if (!tracerouteOutputEl) return; - const lineDiv = document.createElement('div'); - lineDiv.classList.add('hop-line', 'fade-in'); // Animation hinzufügen - - const hopNumSpan = document.createElement('span'); - hopNumSpan.classList.add('hop-number'); - hopNumSpan.textContent = hopData.hop || '?'; - lineDiv.appendChild(hopNumSpan); - - if (hopData.ip) { - const ipSpan = document.createElement('span'); - ipSpan.classList.add('hop-ip'); - ipSpan.textContent = hopData.ip; - lineDiv.appendChild(ipSpan); - if (hopData.hostname) { - const hostSpan = document.createElement('span'); - hostSpan.classList.add('hop-hostname'); - hostSpan.textContent = ` (${hopData.hostname})`; - lineDiv.appendChild(hostSpan); - } - } else if (hopData.rtt && hopData.rtt.every(r => r === '*')) { - const timeoutSpan = document.createElement('span'); - timeoutSpan.classList.add('hop-timeout'); - timeoutSpan.textContent = '* * *'; - lineDiv.appendChild(timeoutSpan); - } else { - lineDiv.appendChild(document.createTextNode(hopData.rawLine || 'Unknown hop format')); - } - - if (hopData.rtt && Array.isArray(hopData.rtt)) { - hopData.rtt.forEach(rtt => { - const rttSpan = document.createElement('span'); - if (rtt === '*') { - rttSpan.classList.add('hop-timeout'); - rttSpan.textContent = ' *'; - } else { - rttSpan.classList.add('hop-rtt'); - rttSpan.textContent = ` ${rtt} ms`; - } - lineDiv.appendChild(rttSpan); - }); - } - tracerouteOutputEl.appendChild(lineDiv); - tracerouteOutputEl.scrollTop = tracerouteOutputEl.scrollHeight; - } - - // --- Port Scan Functions --- - function startPortScan(ip) { - if (!ip) { - showGlobalError('Cannot start port scan: IP address is missing.'); - return; - } - if (!portScanSection || !portScanOutputEl || !portScanLoader || !portScanMessage) return; - - if (portScanEventSource) { - portScanEventSource.close(); - } - - portScanSection.classList.remove('hidden'); - portScanOutputEl.innerHTML = ''; - portScanLoader.classList.remove('hidden'); - portScanMessage.textContent = `Starting port scan for ${ip}...`; - hideGlobalError(); - hideLookupError(); - - const url = `${API_BASE_URL}/port-scan?targetIp=${encodeURIComponent(ip)}`; - portScanEventSource = new EventSource(url); - - portScanEventSource.onopen = () => { - console.log('SSE connection opened for port scan.'); - }; - - portScanEventSource.onerror = (event) => { - console.error('Port Scan EventSource failed:', event); - portScanMessage.textContent = 'Connection error during port scan.'; - portScanLoader.classList.add('hidden'); - portScanEventSource.close(); - }; - - portScanEventSource.addEventListener('info', (event) => { - const infoData = JSON.parse(event.data); - portScanMessage.textContent = infoData.message; - }); - - portScanEventSource.addEventListener('port_status', (event) => { - const portData = JSON.parse(event.data); - displayPortScanResult(portData); - }); - - portScanEventSource.addEventListener('error', (event) => { - const errorData = JSON.parse(event.data); - displayPortScanResult({ error: errorData.error }); - }); - - portScanEventSource.addEventListener('end', (event) => { - const endData = JSON.parse(event.data); - portScanMessage.textContent = endData.message; - portScanLoader.classList.add('hidden'); - portScanEventSource.close(); - }); - } - - function displayPortScanResult(data) { - if (!portScanOutputEl) return; - const lineDiv = document.createElement('div'); - lineDiv.classList.add('mb-1', 'fade-in'); // Animation hinzufügen - - let statusColor = 'text-gray-400'; - let statusText = data.status.toUpperCase(); - if (data.status === 'open') { - statusColor = 'text-green-400'; - statusText = 'OPEN'; - } else if (data.status === 'closed') { - statusColor = 'text-red-400'; - statusText = 'CLOSED'; - } else if (data.status === 'timeout') { - statusColor = 'text-yellow-400'; - statusText = 'TIMEOUT (Filtered?)'; - } - - if (data.error) { - lineDiv.innerHTML = `Error: ${data.error}`; - } else { - lineDiv.innerHTML = `Port ${data.port} (${data.service}): ${statusText}`; - } - - portScanOutputEl.appendChild(lineDiv); - portScanOutputEl.scrollTop = portScanOutputEl.scrollHeight; - } - - - // --- Event Handlers --- - function handleIpClick(event) { - event.preventDefault(); // Verhindert das Standardverhalten des Links (#) - if (currentIp) { - console.log(`User IP link clicked: ${currentIp}. Redirecting to WHOIS lookup...`); - // Leite zur Whois-Seite weiter und übergebe die IP als 'query'-Parameter - window.location.href = `/whois?query=${encodeURIComponent(currentIp)}`; - } else { - console.warn('Cannot redirect to WHOIS: current IP is not available.'); - } - } - - async function handleLookupClick() { - if (!lookupIpInput) return; - const query = lookupIpInput.value.trim(); - if (!query) { - showLookupError('Please enter an IP address or domain name.'); - return; - } - - resetLookupResults(); // Reset results before starting - hideLookupError(); - - // Update URL with the query parameter - updateLookupUrlParams(query); - - if (isValidIpAddress(query)) { - // Input is an IP address - console.log(`Lookup button clicked for IP: ${query}`); - fetchLookupInfo(query); - } else { - // Input is likely a domain name - console.log(`Lookup button clicked for domain: ${query}`); - showLookupStatus(`Resolving domain ${query}...`); // Show status - try { - const resolvedIp = await resolveDomainToIp(query); - if (resolvedIp) { - console.log(`Domain ${query} resolved to ${resolvedIp}. Fetching lookup info...`); - // Optional: Update input field with resolved IP? Maybe not, keep original query. - // lookupIpInput.value = resolvedIp; - fetchLookupInfo(resolvedIp); // Fetch info for the resolved IP - } else { - // Should be caught by the error in resolveDomainToIp, but as a fallback: - showLookupError(`Could not resolve domain ${query} to an IP address.`); - } - } catch (error) { - showLookupError(error.message); // Display resolution error - } finally { - hideLookupStatus(); // Hide status message regardless of outcome - } - } - } - - - function handleLookupPingClick() { - if (currentLookupIp) { - console.log(`Starting ping for looked-up IP: ${currentLookupIp}`); - runLookupPing(currentLookupIp); // Call the new ping function - } - } - - function handleLookupTraceClick() { - if (currentLookupIp) { - console.log(`Starting traceroute for looked-up IP: ${currentLookupIp}`); - startTraceroute(currentLookupIp); - } - } - - function handleLookupScanClick() { - if (currentLookupIp) { - console.log(`Starting port scan for looked-up IP: ${currentLookupIp}`); - startPortScan(currentLookupIp); - } - } - - /** - * Executes IP lookup from URL parameters if they exist - */ - function executeLookupFromUrl() { - const ipParam = getLookupUrlParams(); - if (ipParam && lookupIpInput) { - // Populate the input field - lookupIpInput.value = ipParam; - - // Trigger the lookup - handleLookupClick(); - } - } - - // --- Initial Load & Event Listeners --- - fetchIpInfo(); // Lade Infos zur eigenen IP - fetchVersionInfo(); // Lade Versionsinfo für Footer - - // IP Lookup Listeners (nur wenn Elemente existieren) - if (lookupButton) lookupButton.addEventListener('click', handleLookupClick); - if (lookupIpInput) lookupIpInput.addEventListener('keypress', (event) => { - if (event.key === 'Enter') handleLookupClick(); - }); - if (lookupPingButton) lookupPingButton.addEventListener('click', handleLookupPingClick); - if (lookupTraceButton) lookupTraceButton.addEventListener('click', handleLookupTraceClick); - if (lookupScanButton) lookupScanButton.addEventListener('click', handleLookupScanClick); - - // Der Event Listener für den IP-Link wird jetzt in fetchIpInfo() hinzugefügt, - // nachdem die IP erfolgreich abgerufen wurde. - - // Execute lookup from URL parameters if present - executeLookupFromUrl(); - -}); // End DOMContentLoaded \ No newline at end of file diff --git a/frontend/app/shared.css b/frontend/app/shared.css new file mode 100644 index 0000000..91f4a0a --- /dev/null +++ b/frontend/app/shared.css @@ -0,0 +1,237 @@ +/* ── Spinner ───────────────────────────────────────────────────── */ +.loader { + border: 4px solid rgba(168, 85, 247, 0.1); + border-left-color: #d8b4fe; + border-radius: 50%; + width: 24px; + height: 24px; + animation: spin 1s linear infinite; + flex-shrink: 0; +} +@keyframes spin { to { transform: rotate(360deg); } } + +/* ── Glassmorphism ─────────────────────────────────────────────── */ +.glass-panel { + background: rgba(17, 24, 39, 0.7); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.05); +} +.glass-card { + background: rgba(31, 41, 55, 0.6); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.05); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} +.glass-card:hover { + transform: translateY(-2px); + box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5); +} + +/* ── Page transition animations ───────────────────────────────── */ +@keyframes pageOut { + from { opacity: 1; transform: translateY(0); } + to { opacity: 0; transform: translateY(-8px); } +} +@keyframes pageIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +#app.page-leaving { + animation: pageOut 0.18s ease forwards; + pointer-events: none; +} +#app.page-entering { + animation: pageIn 0.26s ease forwards; +} + +/* ── Content fade-in ───────────────────────────────────────────── */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +.fade-in { animation: fadeIn 0.4s ease-out forwards; } + +/* ── Result pre-block ──────────────────────────────────────────── */ +.result-pre { + white-space: pre-wrap; + word-break: break-all; + font-family: 'Courier New', Courier, monospace; + background-color: rgba(0, 0, 0, 0.3); + color: #e5e7eb; + padding: 1rem; + border-radius: 0.375rem; + max-height: 500px; + overflow-y: auto; + font-size: 0.875rem; + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.3); +} + +/* ── Scrollbar ─────────────────────────────────────────────────── */ +::-webkit-scrollbar { width: 8px; height: 8px; } +::-webkit-scrollbar-track { background: rgba(31, 41, 55, 0.5); } +::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 4px; } +::-webkit-scrollbar-thumb:hover { background: #6b7280; } + +/* ── Navigation ────────────────────────────────────────────────── */ +nav ul { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; } +nav a { + color: #d1d5db; + text-decoration: none; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + transition: all 0.2s ease; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); + display: inline-block; +} +nav a:hover { + color: #fff; + background: rgba(168, 85, 247, 0.2); + border-color: rgba(168, 85, 247, 0.4); + box-shadow: 0 0 15px rgba(168, 85, 247, 0.1); +} +nav a.active-link { + background: rgba(168, 85, 247, 0.3); + color: #fff; + border-color: #a855f7; +} + +/* ── Header ────────────────────────────────────────────────────── */ +header { + background: rgba(31, 41, 55, 0.4); + backdrop-filter: blur(10px); + padding: 1.5rem; + margin-bottom: 2rem; + border-radius: 1rem; + border: 1px solid rgba(255, 255, 255, 0.05); + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + box-shadow: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -1px rgba(0,0,0,.06); +} +@media (min-width: 768px) { + header { flex-direction: row; justify-content: space-between; } +} +header h1 { background-clip: text; } + +/* ── Text gradient ─────────────────────────────────────────────── */ +.text-gradient { + background: linear-gradient(to right, #c084fc, #e879f9); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} +.glitch-text:hover { + text-shadow: 2px 2px 0 rgba(168,85,247,.4), -2px -2px 0 rgba(236,72,153,.4); +} + +/* ── Copy button ───────────────────────────────────────────────── */ +.copy-btn { + display: inline-flex; + align-items: center; + font-size: 0.65rem; + padding: 0.15rem 0.5rem; + border-radius: 0.3rem; + border: 1px solid rgba(168, 85, 247, 0.35); + color: #a78bfa; + background: rgba(168, 85, 247, 0.1); + cursor: pointer; + transition: all 0.15s ease; + user-select: none; + white-space: nowrap; +} +.copy-btn:hover { background: rgba(168, 85, 247, 0.25); color: #c4b5fd; } +.copy-btn.copied { border-color: rgba(52,211,153,.4); color: #34d399; background: rgba(52,211,153,.1); } + +/* ── Stop button ───────────────────────────────────────────────── */ +.stop-btn { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-size: 0.75rem; + padding: 0.25rem 0.75rem; + border-radius: 0.375rem; + border: 1px solid rgba(248,113,113,.4); + color: #f87171; + background: rgba(248,113,113,.1); + cursor: pointer; + transition: all 0.15s ease; +} +.stop-btn:hover { background: rgba(248,113,113,.2); } + +/* ── Home page — IP link ────────────────────────────────────────── */ +#ip-address-link { + cursor: pointer; + text-decoration: none; + position: relative; + display: inline-block; + transition: color 0.3s ease; +} +#ip-address-link::after { + content: ''; + position: absolute; + width: 100%; + transform: scaleX(0); + height: 2px; + bottom: 0; + left: 0; + background-color: #d8b4fe; + transform-origin: bottom right; + transition: transform 0.25s ease-out; +} +#ip-address-link:hover::after { transform: scaleX(1); transform-origin: bottom left; } + +/* ── Home page — Traceroute output ─────────────────────────────── */ +#traceroute-output pre, .result-pre { + white-space: pre-wrap; + word-break: break-all; + font-family: 'Courier New', Courier, monospace; + background-color: rgba(0,0,0,.3); + color: #e5e7eb; + padding: 1rem; + border-radius: 0.375rem; + max-height: 400px; + overflow-y: auto; + font-size: 0.875rem; + border: 1px solid rgba(255,255,255,.05); + box-shadow: inset 0 2px 4px 0 rgba(0,0,0,.3); +} +#traceroute-output .hop-line { margin-bottom: .25rem; padding-left: .5rem; border-left: 2px solid transparent; transition: border-left-color .3s; } +#traceroute-output .hop-line:hover { border-left-color: #a855f7; background: rgba(255,255,255,.02); } +#traceroute-output .hop-number { display: inline-block; width: 30px; text-align: right; margin-right: 15px; color: #6b7280; font-weight: bold; } +#traceroute-output .hop-ip { color: #60a5fa; font-weight: 500; } +#traceroute-output .hop-hostname { color: #c084fc; } +#traceroute-output .hop-rtt { color: #34d399; margin-left: 8px; font-size: .85em; } +#traceroute-output .hop-timeout { color: #f87171; } +#traceroute-output .info-line { color: #fbbf24; font-style: italic; } +#traceroute-output .error-line { color: #f87171; font-weight: bold; border-left: 3px solid #f87171; padding-left: 10px; } +#traceroute-output .end-line { color: #d8b4fe; font-weight: bold; margin-top: 15px; text-transform: uppercase; letter-spacing: .05em; border-top: 1px solid rgba(255,255,255,.1); padding-top: 10px; } + +/* ── Home page — Maps ───────────────────────────────────────────── */ +#map { height: 300px; } +#lookup-map { height: 250px; } + +/* ── ASN page — Graph ───────────────────────────────────────────── */ +#graph-container { width: 100%; height: 600px; background: rgba(0,0,0,.3); border-radius: .75rem; border: 1px solid rgba(255,255,255,.06); overflow: hidden; position: relative; } +#graph-svg { width: 100%; height: 100%; cursor: grab; } +#graph-svg:active { cursor: grabbing; } +.node-center circle { fill: #a855f7; stroke: #d8b4fe; stroke-width: 2.5; } +.node-upstream circle { fill: #3b82f6; stroke: #93c5fd; stroke-width: 1.5; } +.node-downstream circle { fill: #10b981; stroke: #6ee7b7; stroke-width: 1.5; } +.node-tier1 circle { fill: #6b7280; stroke: #9ca3af; stroke-width: 1.5; } +.node text { fill: #e5e7eb; font-size: 11px; font-family: 'Courier New',monospace; pointer-events: none; text-anchor: middle; } +.node:hover circle { filter: brightness(1.4); cursor: pointer; } +.link { stroke: rgba(255,255,255,.12); stroke-linecap: round; } +.link-upstream { stroke: rgba(59,130,246,.35); } +.link-tier1 { stroke: rgba(107,114,128,.3); stroke-dasharray: 4 3; } +.link-downstream { stroke: rgba(16,185,129,.35); } +#graph-tooltip { position: absolute; pointer-events: none; background: rgba(17,24,39,.95); backdrop-filter: blur(8px); border: 1px solid rgba(168,85,247,.4); border-radius: .5rem; padding: .6rem .9rem; font-size: 12px; color: #e5e7eb; max-width: 220px; z-index: 50; opacity: 0; transition: opacity .15s; } +.prefix-tag { display: inline-block; font-family: monospace; font-size: 11px; background: rgba(168,85,247,.15); color: #c084fc; border: 1px solid rgba(168,85,247,.3); border-radius: 4px; padding: 2px 6px; margin: 2px; } +.ixp-row { border-bottom: 1px solid rgba(255,255,255,.05); } +.ixp-row:last-child { border-bottom: none; } + +.hidden { display: none !important; } diff --git a/frontend/app/shared.js b/frontend/app/shared.js new file mode 100644 index 0000000..c4cee0c --- /dev/null +++ b/frontend/app/shared.js @@ -0,0 +1,21 @@ +export const API = '/api'; + +export function setupCopyBtn(btn, getText) { + if (!btn) return; + btn.addEventListener('click', () => { + const text = getText(); + if (!text) return; + navigator.clipboard.writeText(text).then(() => { + const orig = btn.textContent; + btn.textContent = '✓ copied'; + btn.classList.add('copied'); + setTimeout(() => { btn.textContent = orig; btn.classList.remove('copied'); }, 1500); + }).catch(() => {}); + }); +} + +export function showError(el, msg) { + if (!el) return; + el.textContent = msg ? `Error: ${msg}` : ''; + el.classList.toggle('hidden', !msg); +} diff --git a/frontend/app/subnet-calculator.html b/frontend/app/subnet-calculator.html deleted file mode 100644 index 00f1b55..0000000 --- a/frontend/app/subnet-calculator.html +++ /dev/null @@ -1,352 +0,0 @@ - - - - - - - IP Subnetz Rechner - uTools - - - - - - - - -
    -

    uTools Network - Suite

    - -
    - -
    - -

    IP Subnetz Rechner

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

    - Beispiel-Subnetze (Private Adressbereiche)

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    BereichCIDRSubnetzmaskeBeschreibungAktion
    192.168.0.0 - 192.168.255.255/16 (Gesamt)255.255.0.0Klasse C (oft als /24 genutzt)Beispiel /24
    172.16.0.0 - 172.31.255.255/12 (Gesamt)255.240.0.0Klasse BBeispiel /16
    10.0.0.0 - 10.255.255.255/8 (Gesamt)255.0.0.0Klasse ABeispiel /8
    -
    -

    Klicken Sie auf "Beispiel", um die Felder oben auszufüllen und - die Berechnung zu starten.

    -
    - - - - -
    - - - - - - - - - \ No newline at end of file diff --git a/frontend/app/subnet-calculator.js b/frontend/app/subnet-calculator.js deleted file mode 100644 index 8f1a884..0000000 --- a/frontend/app/subnet-calculator.js +++ /dev/null @@ -1,211 +0,0 @@ -// Event Listener hinzufügen, sobald das DOM geladen ist -document.addEventListener('DOMContentLoaded', () => { - const form = document.getElementById('subnet-form'); - if (form) { - form.addEventListener('submit', handleSubnetCalculation); - } else { - console.error("Subnetz-Formular (ID: subnet-form) nicht gefunden!"); - } -}); - -// Funktion zur Behandlung der Subnetzberechnung bei Formularübermittlung -function handleSubnetCalculation(event) { - event.preventDefault(); // Verhindert das Neuladen der Seite - clearResults(); // Ergebnisse zuerst löschen/verstecken - - const ipAddressInput = document.getElementById('ip-address').value.trim(); - const cidrInput = document.getElementById('cidr').value.trim(); - const resultsDiv = document.getElementById('results'); // Ergebnis-Div holen - - // Einfache Validierung - if (!isValidIP(ipAddressInput)) { - alert("Bitte geben Sie eine gültige IPv4-Adresse ein."); - return; - } - - let cidr; - let subnetMask; - - // Prüfen, ob CIDR oder Subnetzmaske eingegeben wurde - if (cidrInput.includes('.')) { // Annahme: Subnetzmaske im Format xxx.xxx.xxx.xxx - if (!isValidIP(cidrInput)) { - alert("Bitte geben Sie eine gültige Subnetzmaske ein."); - return; - } - subnetMask = cidrInput; - cidr = maskToCidr(subnetMask); - if (cidr === null) { - alert("Ungültige Subnetzmaske. Sie muss aus einer kontinuierlichen Folge von Einsen gefolgt von Nullen bestehen (z.B. 255.255.255.0, nicht 255.255.0.255)."); - return; - } - } else { // Annahme: CIDR-Notation - cidr = parseInt(cidrInput, 10); - if (isNaN(cidr) || cidr < 0 || cidr > 32) { - alert("Bitte geben Sie einen gültigen CIDR-Wert (0-32) ein."); - return; - } - subnetMask = cidrToMask(cidr); - if (subnetMask === null) { - alert("Interner Fehler bei der Umwandlung von CIDR zu Maske."); - return; - } - } - - // Berechnung durchführen und Ergebnisse anzeigen - try { - const results = calculateSubnet(ipAddressInput, cidr); - displayResults(results, subnetMask); - if (resultsDiv) { - resultsDiv.classList.remove('hidden'); // Ergebnisbereich sichtbar machen - } else { - console.error("Ergebnis-Div (ID: results) nicht gefunden!"); - } - } catch (error) { - console.error("Fehler bei der Subnetzberechnung:", error); - alert("Fehler bei der Berechnung: " + error.message); - clearResults(); - } -} - -// --- Validierungs- und Hilfsfunktionen --- - -function isValidIP(ip) { - const ipPattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - return ipPattern.test(ip); -} - -function ipToBinary(ip) { - return ip.split('.').map(octet => parseInt(octet, 10).toString(2).padStart(8, '0')).join(''); -} - -function binaryToIp(binary) { - if (binary.length !== 32) return null; - const octets = []; - for (let i = 0; i < 32; i += 8) { - octets.push(parseInt(binary.substring(i, i + 8), 2)); - } - return octets.join('.'); -} - -function cidrToMask(cidr) { - if (cidr < 0 || cidr > 32) return null; - const maskBinary = '1'.repeat(cidr) + '0'.repeat(32 - cidr); - return binaryToIp(maskBinary); -} - -function maskToCidr(mask) { - if (!isValidIP(mask)) return null; - const binaryMask = ipToBinary(mask); - let encounteredZero = false; - for (let i = 0; i < 32; i++) { - if (binaryMask[i] === '1') { - if (encounteredZero) return null; - } else { - encounteredZero = true; - } - } - let cidr = 0; - for(let i = 0; i < 32; i++) { - if (binaryMask[i] === '1') { - cidr++; - } else { - break; - } - } - return cidr; -} - -// --- Berechnungsfunktion --- - -function calculateSubnet(ip, cidr) { - const ipBinary = ipToBinary(ip); - const maskBinary = '1'.repeat(cidr) + '0'.repeat(32 - cidr); - - // Netzwerkadresse berechnen (Bitweises UND von IP und Maske) - let networkBinary = ''; - for (let i = 0; i < 32; i++) { - networkBinary += (parseInt(ipBinary[i], 10) & parseInt(maskBinary[i], 10)).toString(); - } - const networkAddress = binaryToIp(networkBinary); - const networkNum = parseInt(networkBinary, 2); // Netzwerkadresse als Zahl - - // Broadcast-Adresse berechnen (Netzwerk-Teil + Host-Teil mit Einsen) - Korrigierte Methode - const hostBitsCount = 32 - cidr; - let broadcastBinary = networkBinary.substring(0, cidr) + '1'.repeat(hostBitsCount); - // Sicherstellen, dass die Länge 32 Bit beträgt (sollte sie aber ohnehin) - broadcastBinary = broadcastBinary.padEnd(32, '1'); // Auffüllen mit 1, falls Länge < 32 (unwahrscheinlich) - - const broadcastAddress = binaryToIp(broadcastBinary); - // broadcastNum wird für die letzte Host-Adresse benötigt - const broadcastNum = parseInt(broadcastBinary, 2); - - // Anzahl der Hosts - const hostBits = 32 - cidr; // hostBitsCount umbenannt für Konsistenz - let hostCount = 0; - if (hostBits >= 2) { // Mindestens /30 für 2 Hosts (-2) - hostCount = Math.pow(2, hostBits) - 2; - } else if (hostBits === 1) { // /31 hat 2 Adressen, beide nutzbar (RFC 3021) - hostCount = 2; - } else { // /32 hat nur 1 Adresse - hostCount = 1; - } - - // Erste Host-Adresse - let firstHost = '-'; - if (hostBits >= 2) { // /30 oder größer: Netzwerkadresse + 1 - // Sicherstellen, dass die Addition korrekt behandelt wird (als Zahl) - const firstHostNum = networkNum + 1; - const firstHostBinary = firstHostNum.toString(2).padStart(32, '0'); - firstHost = binaryToIp(firstHostBinary); - } else if (cidr === 31) { // /31: Die erste Adresse des /31 - firstHost = networkAddress; - } else { // /32: Nur die eine Adresse - firstHost = networkAddress; - } - - // Letzte Host-Adresse - let lastHost = '-'; - if (hostBits >= 2) { // /30 oder größer: Broadcast-Adresse - 1 - // Sicherstellen, dass die Subtraktion korrekt behandelt wird (als Zahl) - const lastHostNum = broadcastNum - 1; - const lastHostBinary = lastHostNum.toString(2).padStart(32, '0'); - lastHost = binaryToIp(lastHostBinary); - } else if (cidr === 31) { // /31: Die zweite Adresse des /31 - lastHost = broadcastAddress; - } else { // /32: Nur die eine Adresse - lastHost = networkAddress; - } - - return { - networkAddress, - broadcastAddress, - hostCount, - firstHost, - lastHost - }; -} - -// --- Anzeige-Funktionen --- - -function displayResults(results, subnetMask) { - document.getElementById('network-address').textContent = results.networkAddress; - document.getElementById('broadcast-address').textContent = results.broadcastAddress; - document.getElementById('host-count').textContent = results.hostCount >= 0 ? results.hostCount.toLocaleString() : '-'; - document.getElementById('first-host').textContent = results.firstHost; - document.getElementById('last-host').textContent = results.lastHost; - document.getElementById('subnet-mask').textContent = subnetMask; -} - -function clearResults() { - document.getElementById('network-address').textContent = '-'; - document.getElementById('broadcast-address').textContent = '-'; - document.getElementById('host-count').textContent = '-'; - document.getElementById('first-host').textContent = '-'; - document.getElementById('last-host').textContent = '-'; - document.getElementById('subnet-mask').textContent = '-'; - - const resultsDiv = document.getElementById('results'); - if (resultsDiv && !resultsDiv.classList.contains('hidden')) { - resultsDiv.classList.add('hidden'); - } -} \ No newline at end of file diff --git a/frontend/app/whois-lookup.html b/frontend/app/whois-lookup.html deleted file mode 100644 index 17f8175..0000000 --- a/frontend/app/whois-lookup.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - - WHOIS Lookup - uTools - - - - - - - - -
    -

    uTools Network - Suite

    - -
    - -
    - -

    WHOIS Lookup

    - - -
    -
    - - -
    - - -
    - - - - - - - -
    - - - - - - \ No newline at end of file diff --git a/frontend/app/whois-lookup.js b/frontend/app/whois-lookup.js deleted file mode 100644 index d08ffb3..0000000 --- a/frontend/app/whois-lookup.js +++ /dev/null @@ -1,151 +0,0 @@ -// frontend/whois-lookup.js -document.addEventListener('DOMContentLoaded', () => { - // --- DOM Elements (WHOIS Lookup) --- - const whoisQueryInput = document.getElementById('whois-query-input'); - const whoisLookupButton = document.getElementById('whois-lookup-button'); - const whoisLookupErrorEl = document.getElementById('whois-lookup-error'); - const whoisLookupResultsSection = document.getElementById('whois-lookup-results-section'); - const whoisLookupQueryEl = document.getElementById('whois-lookup-query'); - const whoisLookupLoader = document.getElementById('whois-lookup-loader'); - const whoisLookupOutputEl = document.getElementById('whois-lookup-output'); - - // --- DOM Elements (Common) --- - const globalErrorEl = document.getElementById('global-error'); - const commitShaEl = document.getElementById('commit-sha'); - - // --- Configuration --- - const API_BASE_URL = '/api'; // Anpassen, falls nötig - - // --- Helper Functions --- - - /** Zeigt globale Fehler an */ - function showGlobalError(message) { - if (!globalErrorEl) return; - globalErrorEl.textContent = `Error: ${message}`; - globalErrorEl.classList.remove('hidden'); - } - - /** Versteckt globale Fehler */ - function hideGlobalError() { - if (!globalErrorEl) return; - globalErrorEl.classList.add('hidden'); - } - - /** - * Generische Funktion zum Abrufen und Anzeigen von Lookup-Ergebnissen. - * @param {string} endpoint - Der API-Endpunkt (z.B. '/whois-lookup'). - * @param {object} params - Query-Parameter als Objekt (z.B. { query: '...' }). - * @param {HTMLElement} resultsSection - Der Container für die Ergebnisse. - * @param {HTMLElement} loaderElement - Das Loader-Element. - * @param {HTMLElement} errorElement - Das Fehleranzeige-Element für diesen Lookup. - * @param {HTMLElement} queryElement - Das Element zur Anzeige der Suchanfrage. - * @param {HTMLElement} outputElement - Das Element zur Anzeige der Ergebnisse (
     oder 

    ). - * @param {function} displayFn - Funktion zur Formatierung und Anzeige der Daten im outputElement. - */ - async function fetchAndDisplay(endpoint, params, resultsSection, loaderElement, errorElement, queryElement, outputElement, displayFn) { - // Reset animation - resultsSection.classList.remove('fade-in'); - void resultsSection.offsetWidth; // Trigger reflow - resultsSection.classList.add('fade-in'); - - resultsSection.classList.remove('hidden'); - loaderElement.classList.remove('hidden'); - errorElement.classList.add('hidden'); - outputElement.textContent = ''; // Clear previous results - if (queryElement) queryElement.textContent = Object.values(params).join(', '); // Display query - hideGlobalError(); // Hide global errors before new request - - const urlParams = new URLSearchParams(params); - const url = `${API_BASE_URL}${endpoint}?${urlParams.toString()}`; - - try { - const response = await fetch(url); - const data = await response.json(); - - if (!response.ok || !data.success) { - throw new Error(data.error || `Request failed with status ${response.status}`); - } - - console.log(`Received ${endpoint} data:`, data); - displayFn(data, outputElement); // Call the specific display function - - } catch (error) { - console.error(`Failed to fetch ${endpoint}:`, error); - errorElement.textContent = `Error: ${error.message}`; - errorElement.classList.remove('hidden'); - outputElement.textContent = ''; // Clear output on error - } finally { - loaderElement.classList.add('hidden'); - } - } - - /** Ruft die Versionsinformationen (Commit SHA) ab */ - async function fetchVersionInfo() { - if (!commitShaEl) return; // Don't fetch if element doesn't exist - try { - const response = await fetch(`${API_BASE_URL}/version`); - if (!response.ok) throw new Error(`Network response: ${response.statusText} (${response.status})`); - const data = await response.json(); - commitShaEl.textContent = data.commitSha || 'unknown'; - } catch (error) { - console.error('Failed to fetch version info:', error); - commitShaEl.textContent = 'error'; - // Optionally show global error - // showGlobalError(`Could not load version info: ${error.message}`); - } - } - - // --- WHOIS Lookup Specific Functions --- - function displayWhoisResults(data, outputEl) { - // WHOIS data can be large and unstructured, display as raw text - if (typeof data.result === 'string') { - outputEl.textContent = data.result; // Display raw text - } else { - // Fallback if the result is not a string (shouldn't happen with current backend) - outputEl.textContent = JSON.stringify(data.result, null, 2); - } - } - - function handleWhoisLookupClick() { - const query = whoisQueryInput.value.trim(); - if (!query) { - whoisLookupErrorEl.textContent = 'Please enter a domain or IP address.'; - whoisLookupErrorEl.classList.remove('hidden'); - return; - } - fetchAndDisplay( - '/whois-lookup', - { query }, - whoisLookupResultsSection, - whoisLookupLoader, - whoisLookupErrorEl, - whoisLookupQueryEl, - whoisLookupOutputEl, - displayWhoisResults - ); - } - - /** Prüft URL-Parameter und startet ggf. den Lookup */ - function checkUrlParamsAndLookup() { - const urlParams = new URLSearchParams(window.location.search); - const queryFromUrl = urlParams.get('query'); - - if (queryFromUrl && whoisQueryInput) { - console.log(`Found query parameter in URL: ${queryFromUrl}`); - whoisQueryInput.value = queryFromUrl; // Set input field value - handleWhoisLookupClick(); // Trigger the lookup - } - } - - // --- Initial Load & Event Listeners --- - fetchVersionInfo(); // Lade Versionsinfo für Footer - - whoisLookupButton.addEventListener('click', handleWhoisLookupClick); - whoisQueryInput.addEventListener('keypress', (event) => { - if (event.key === 'Enter') handleWhoisLookupClick(); - }); - - // Prüfe URL-Parameter nach dem Setup der Listener - checkUrlParamsAndLookup(); - -}); // End DOMContentLoaded \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 8383ff2..9d7ea03 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -10,22 +10,9 @@ server { access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; - # Clean URL rewrites - Map short URLs to actual HTML files - rewrite ^/dns$ /dns-lookup.html last; - rewrite ^/dns-lookup$ /dns-lookup.html last; - rewrite ^/whois$ /whois-lookup.html last; - rewrite ^/whois-lookup$ /whois-lookup.html last; - rewrite ^/mac$ /mac-lookup.html last; - rewrite ^/mac-lookup$ /mac-lookup.html last; - rewrite ^/subnet$ /subnet-calculator.html last; - rewrite ^/subnet-calculator$ /subnet-calculator.html last; - rewrite ^/asn$ /asn-lookup.html last; - rewrite ^/asn-lookup$ /asn-lookup.html last; - - # Statische Dateien direkt ausliefern + # SPA: all routes fall back to index.html; static assets are served directly location / { - # First try the exact URI, then with .html, then fall back to index.html - try_files $uri $uri.html $uri/ /index.html; + try_files $uri $uri/ /index.html; } # API-Anfragen an den Backend-Service weiterleiten