commit f0d949c2a60c8ae9dd1981ce52b97b6d798819ec Author: ARCHITECT Date: Sat Jan 17 22:55:55 2026 +0000 Initial commit: architect-frontend infrastructure viewer - Vite + TypeScript + D3.js setup - Infrastructure graph view (servers, services, connections) - Tables graph view with categories (core, directus, storage, etc.) - PostgREST API integration - Deploy script for Caddy static serving Co-Authored-By: Claude Opus 4.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5706ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.DS_Store +*.log +.env +.env.local diff --git a/architect-frontend.service b/architect-frontend.service new file mode 100644 index 0000000..6f74e43 --- /dev/null +++ b/architect-frontend.service @@ -0,0 +1,14 @@ +[Unit] +Description=TZZR Architect Frontend +After=network.target + +[Service] +Type=simple +User=architect +WorkingDirectory=/home/architect/captain-claude/architect-frontend +ExecStart=/home/architect/captain-claude/architect-frontend/serve.sh +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..cbeb5a7 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Deploy frontend ARCHITECT a /opt/architect/web/ + +set -e + +DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$DIR" + +echo "Building..." +npm run build + +echo "Deploying..." +ssh -i ~/.ssh/tzzr root@69.62.126.110 "rm -rf /opt/architect/web/* && cp -r $DIR/dist/* /opt/architect/web/" + +echo "Done: https://tzzrarchitect.me" diff --git a/index.html b/index.html new file mode 100644 index 0000000..28b242e --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + ARCHITECT + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..01399c9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1755 @@ +{ + "name": "architect-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "architect-frontend", + "version": "1.0.0", + "dependencies": { + "d3": "^7.9.0" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "typescript": "^5.4.0", + "vite": "^5.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7facf1e --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "architect-frontend", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint src --ext .ts" + }, + "dependencies": { + "d3": "^7.9.0" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "typescript": "^5.4.0", + "vite": "^5.4.0" + } +} diff --git a/serve.sh b/serve.sh new file mode 100755 index 0000000..3e7258b --- /dev/null +++ b/serve.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd /home/architect/captain-claude/architect-frontend +exec npx serve dist -l 5050 diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..2de3b26 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1 @@ +export * from './infrastructure.ts'; diff --git a/src/api/infrastructure.ts b/src/api/infrastructure.ts new file mode 100644 index 0000000..5c83400 --- /dev/null +++ b/src/api/infrastructure.ts @@ -0,0 +1,176 @@ +import type { Server, Service, Connection } from '../types/index.ts'; + +const API_BASE = '/api'; + +// Tipos de respuesta de la API (snake_case) +interface ApiServer { + id: string; + name: string; + ip: string; + description: string; + status: string; + suspended: boolean; +} + +interface ApiService { + id: string; + name: string; + server_id: string; + type: string; + status: string; + url?: string; + uptime?: string; +} + +interface ApiConnection { + id: number; + source: string; + target: string; + type: string; + label?: string; +} + +interface ApiColumn { + id: number; + schema_id: string; + table_name: string; + column_name: string; + data_type: string; + is_nullable: boolean; + is_primary: boolean; + default_value?: string; + description?: string; + ordinal_position: number; +} + +export interface Column { + name: string; + dataType: string; + nullable: boolean; + primary: boolean; + defaultValue?: string; + description?: string; + position: number; +} + +// Mapear respuesta API a tipos del frontend +const mapServer = (s: ApiServer): Server => ({ + id: s.id, + name: s.name, + ip: s.ip || '', + description: s.description || '', + status: s.status as Server['status'], + suspended: s.suspended +}); + +const mapService = (s: ApiService): Service => ({ + id: s.id, + name: s.name, + serverId: s.server_id, + type: s.type as Service['type'], + status: s.status as Service['status'], + url: s.url, + uptime: s.uptime +}); + +const mapConnection = (c: ApiConnection): Connection => ({ + source: c.source, + target: c.target, + type: c.type as Connection['type'], + label: c.label +}); + +// Fetch con manejo de errores +async function apiFetch(endpoint: string): Promise { + const res = await fetch(`${API_BASE}${endpoint}`); + if (!res.ok) throw new Error(`API error: ${res.status}`); + return res.json(); +} + +export const fetchServers = async (): Promise => { + const data = await apiFetch('/servers'); + return data.map(mapServer); +}; + +export const fetchServices = async (): Promise => { + const data = await apiFetch('/services'); + return data.map(mapService); +}; + +export const fetchConnections = async (): Promise => { + const data = await apiFetch('/connections'); + return data.map(mapConnection); +}; + +export const fetchInfrastructure = async () => { + const [servers, services, connections] = await Promise.all([ + fetchServers(), + fetchServices(), + fetchConnections() + ]); + return { servers, services, connections }; +}; + +// Fetch columns for a specific table +export const fetchColumns = async (schemaId: string, tableName: string): Promise => { + const data = await apiFetch(`/columns?schema_id=eq.${schemaId}&table_name=eq.${tableName}&order=ordinal_position`); + return data.map(c => ({ + name: c.column_name, + dataType: c.data_type, + nullable: c.is_nullable, + primary: c.is_primary, + defaultValue: c.default_value, + description: c.description, + position: c.ordinal_position + })); +}; + +// Types for tables API +interface ApiTable { + id: number; + schema_id: string; + name: string; + category: string; + description?: string; +} + +interface ApiTableRelation { + id: number; + schema_id: string; + source_table: string; + target_table: string; + relation_type: string; + label?: string; +} + +export interface TableInfo { + name: string; + category: string; + database: string; +} + +export interface TableRelationInfo { + from: string; + to: string; + type: 'fk' | 'ref' | 'logical'; +} + +// Fetch tables for a specific schema +export const fetchTables = async (schemaId: string): Promise => { + const data = await apiFetch(`/tables?schema_id=eq.${schemaId}&order=category,name`); + return data.map(t => ({ + name: t.name, + category: t.category, + database: schemaId + })); +}; + +// Fetch table relations for a specific schema +export const fetchTableRelations = async (schemaId: string): Promise => { + const data = await apiFetch(`/table_relations?schema_id=eq.${schemaId}`); + return data.map(r => ({ + from: r.source_table, + to: r.target_table, + type: r.relation_type as 'fk' | 'ref' | 'logical' + })); +}; diff --git a/src/api/schemas.ts b/src/api/schemas.ts new file mode 100644 index 0000000..6949992 --- /dev/null +++ b/src/api/schemas.ts @@ -0,0 +1,509 @@ +export interface TableSchema { + name: string; + category: 'core' | 'directus' | 'api' | 'graph' | 'tree' | 'library' | 'data' | 'system' | 'repo' | 'user' | 'action' | 'auth' | 'agent' | 'context' | 'creds' | 'comm' | 'storage'; + database: string; +} + +export interface TableRelation { + from: string; + to: string; + type: 'fk' | 'ref' | 'logical'; +} + +export interface DBSchema { + tables: TableSchema[]; + relations: TableRelation[]; +} + +// HST Database Schema (hst_images) +const hstTables: TableSchema[] = [ + // Core data tables + { name: 'hst', category: 'core', database: 'hst_images' }, + { name: 'flg', category: 'core', database: 'hst_images' }, + { name: 'itm', category: 'core', database: 'hst_images' }, + { name: 'loc', category: 'core', database: 'hst_images' }, + { name: 'ply', category: 'core', database: 'hst_images' }, + { name: 'atc', category: 'core', database: 'hst_images' }, + + // API views + { name: 'api_tags', category: 'api', database: 'hst_images' }, + { name: 'api_groups', category: 'api', database: 'hst_images' }, + { name: 'api_graph_stats', category: 'api', database: 'hst_images' }, + { name: 'api_tree_roots', category: 'api', database: 'hst_images' }, + { name: 'api_library_list', category: 'api', database: 'hst_images' }, + { name: 'api_library_list_hst', category: 'api', database: 'hst_images' }, + { name: 'api_library_list_flg', category: 'api', database: 'hst_images' }, + { name: 'api_library_list_itm', category: 'api', database: 'hst_images' }, + { name: 'api_library_list_loc', category: 'api', database: 'hst_images' }, + { name: 'api_library_list_ply', category: 'api', database: 'hst_images' }, + + // Graph tables + { name: 'graph_hst', category: 'graph', database: 'hst_images' }, + { name: 'graph_flg', category: 'graph', database: 'hst_images' }, + { name: 'graph_itm', category: 'graph', database: 'hst_images' }, + { name: 'graph_loc', category: 'graph', database: 'hst_images' }, + { name: 'graph_ply', category: 'graph', database: 'hst_images' }, + + // Tree tables + { name: 'tree_hst', category: 'tree', database: 'hst_images' }, + { name: 'tree_flg', category: 'tree', database: 'hst_images' }, + { name: 'tree_itm', category: 'tree', database: 'hst_images' }, + { name: 'tree_loc', category: 'tree', database: 'hst_images' }, + { name: 'tree_ply', category: 'tree', database: 'hst_images' }, + + // Library tables + { name: 'library_hst', category: 'library', database: 'hst_images' }, + { name: 'library_flg', category: 'library', database: 'hst_images' }, + { name: 'library_itm', category: 'library', database: 'hst_images' }, + { name: 'library_loc', category: 'library', database: 'hst_images' }, + { name: 'library_ply', category: 'library', database: 'hst_images' }, + + // Rules + { name: 'set_hst_rules', category: 'system', database: 'hst_images' }, + + // Directus system tables + { name: 'directus_users', category: 'directus', database: 'hst_images' }, + { name: 'directus_roles', category: 'directus', database: 'hst_images' }, + { name: 'directus_files', category: 'directus', database: 'hst_images' }, + { name: 'directus_collections', category: 'directus', database: 'hst_images' }, + { name: 'directus_fields', category: 'directus', database: 'hst_images' }, + { name: 'directus_relations', category: 'directus', database: 'hst_images' }, + { name: 'directus_flows', category: 'directus', database: 'hst_images' }, + { name: 'directus_operations', category: 'directus', database: 'hst_images' }, +]; + +const hstRelations: TableRelation[] = [ + // Core -> Graph (cada entidad tiene su grafo) + { from: 'hst', to: 'graph_hst', type: 'ref' }, + { from: 'flg', to: 'graph_flg', type: 'ref' }, + { from: 'itm', to: 'graph_itm', type: 'ref' }, + { from: 'loc', to: 'graph_loc', type: 'ref' }, + { from: 'ply', to: 'graph_ply', type: 'ref' }, + + // Core -> Tree (cada entidad tiene su árbol) + { from: 'hst', to: 'tree_hst', type: 'ref' }, + { from: 'flg', to: 'tree_flg', type: 'ref' }, + { from: 'itm', to: 'tree_itm', type: 'ref' }, + { from: 'loc', to: 'tree_loc', type: 'ref' }, + { from: 'ply', to: 'tree_ply', type: 'ref' }, + + // Core -> Library (cada entidad tiene su biblioteca) + { from: 'hst', to: 'library_hst', type: 'ref' }, + { from: 'flg', to: 'library_flg', type: 'ref' }, + { from: 'itm', to: 'library_itm', type: 'ref' }, + { from: 'loc', to: 'library_loc', type: 'ref' }, + { from: 'ply', to: 'library_ply', type: 'ref' }, + + // Relaciones entre entidades core + { from: 'atc', to: 'hst', type: 'fk' }, + { from: 'flg', to: 'hst', type: 'ref' }, + { from: 'itm', to: 'hst', type: 'ref' }, + { from: 'loc', to: 'hst', type: 'ref' }, + { from: 'ply', to: 'hst', type: 'ref' }, + + // API views -> Core (vistas que dependen de tablas core) + { from: 'api_tags', to: 'hst', type: 'logical' }, + { from: 'api_tags', to: 'flg', type: 'logical' }, + { from: 'api_tags', to: 'itm', type: 'logical' }, + { from: 'api_groups', to: 'hst', type: 'logical' }, + { from: 'api_graph_stats', to: 'graph_hst', type: 'logical' }, + { from: 'api_graph_stats', to: 'graph_flg', type: 'logical' }, + { from: 'api_tree_roots', to: 'tree_hst', type: 'logical' }, + { from: 'api_tree_roots', to: 'tree_flg', type: 'logical' }, + { from: 'api_library_list', to: 'library_hst', type: 'logical' }, + { from: 'api_library_list_hst', to: 'library_hst', type: 'logical' }, + { from: 'api_library_list_flg', to: 'library_flg', type: 'logical' }, + { from: 'api_library_list_itm', to: 'library_itm', type: 'logical' }, + { from: 'api_library_list_loc', to: 'library_loc', type: 'logical' }, + { from: 'api_library_list_ply', to: 'library_ply', type: 'logical' }, + + // System -> Core + { from: 'set_hst_rules', to: 'hst', type: 'ref' }, + + // Directus system + { from: 'directus_users', to: 'directus_roles', type: 'fk' }, + { from: 'directus_fields', to: 'directus_collections', type: 'fk' }, + { from: 'directus_relations', to: 'directus_collections', type: 'fk' }, + { from: 'directus_relations', to: 'directus_fields', type: 'fk' }, + { from: 'directus_operations', to: 'directus_flows', type: 'fk' }, + { from: 'directus_files', to: 'directus_users', type: 'fk' }, +]; + +// DECK Database Schema (shlink + deck) +const deckTables: TableSchema[] = [ + { name: 'short_urls', category: 'core', database: 'shlink' }, + { name: 'visits', category: 'data', database: 'shlink' }, + { name: 'visit_locations', category: 'data', database: 'shlink' }, + { name: 'tags', category: 'core', database: 'shlink' }, + { name: 'short_urls_in_tags', category: 'data', database: 'shlink' }, + { name: 'domains', category: 'system', database: 'shlink' }, + { name: 'api_keys', category: 'system', database: 'shlink' }, + { name: 'api_key_roles', category: 'system', database: 'shlink' }, + { name: 'short_url_visits_counts', category: 'data', database: 'shlink' }, + { name: 'orphan_visits_counts', category: 'data', database: 'shlink' }, + { name: 'redirect_conditions', category: 'system', database: 'shlink' }, + { name: 'short_url_redirect_rules', category: 'system', database: 'shlink' }, + { name: 'migrations', category: 'system', database: 'shlink' }, + { name: 'mail_log', category: 'data', database: 'deck' }, +]; + +const deckRelations: TableRelation[] = [ + { from: 'visits', to: 'short_urls', type: 'fk' }, + { from: 'visits', to: 'visit_locations', type: 'fk' }, + { from: 'short_urls_in_tags', to: 'short_urls', type: 'fk' }, + { from: 'short_urls_in_tags', to: 'tags', type: 'fk' }, + { from: 'short_url_visits_counts', to: 'short_urls', type: 'fk' }, + { from: 'api_key_roles', to: 'api_keys', type: 'fk' }, + { from: 'short_urls', to: 'domains', type: 'fk' }, +]; + +// GITEA Database Schema +const giteaTables: TableSchema[] = [ + // Repository tables + { name: 'repository', category: 'repo', database: 'gitea' }, + { name: 'branch', category: 'repo', database: 'gitea' }, + { name: 'commit_status', category: 'repo', database: 'gitea' }, + { name: 'commit_status_index', category: 'repo', database: 'gitea' }, + { name: 'deploy_key', category: 'repo', database: 'gitea' }, + { name: 'lfs_lock', category: 'repo', database: 'gitea' }, + { name: 'lfs_meta_object', category: 'repo', database: 'gitea' }, + { name: 'mirror', category: 'repo', database: 'gitea' }, + { name: 'protected_branch', category: 'repo', database: 'gitea' }, + { name: 'protected_tag', category: 'repo', database: 'gitea' }, + { name: 'push_mirror', category: 'repo', database: 'gitea' }, + { name: 'release', category: 'repo', database: 'gitea' }, + { name: 'repo_archiver', category: 'repo', database: 'gitea' }, + { name: 'repo_indexer_status', category: 'repo', database: 'gitea' }, + { name: 'repo_license', category: 'repo', database: 'gitea' }, + { name: 'repo_redirect', category: 'repo', database: 'gitea' }, + { name: 'repo_topic', category: 'repo', database: 'gitea' }, + { name: 'repo_transfer', category: 'repo', database: 'gitea' }, + { name: 'repo_unit', category: 'repo', database: 'gitea' }, + { name: 'star', category: 'repo', database: 'gitea' }, + { name: 'topic', category: 'repo', database: 'gitea' }, + { name: 'watch', category: 'repo', database: 'gitea' }, + + // User tables + { name: 'user', category: 'user', database: 'gitea' }, + { name: 'email_address', category: 'user', database: 'gitea' }, + { name: 'email_hash', category: 'user', database: 'gitea' }, + { name: 'follow', category: 'user', database: 'gitea' }, + { name: 'gpg_key', category: 'user', database: 'gitea' }, + { name: 'gpg_key_import', category: 'user', database: 'gitea' }, + { name: 'public_key', category: 'user', database: 'gitea' }, + { name: 'two_factor', category: 'user', database: 'gitea' }, + { name: 'user_badge', category: 'user', database: 'gitea' }, + { name: 'user_blocking', category: 'user', database: 'gitea' }, + { name: 'user_open_id', category: 'user', database: 'gitea' }, + { name: 'user_redirect', category: 'user', database: 'gitea' }, + { name: 'user_setting', category: 'user', database: 'gitea' }, + { name: 'webauthn_credential', category: 'user', database: 'gitea' }, + + // Action tables (CI/CD) + { name: 'action', category: 'action', database: 'gitea' }, + { name: 'action_artifact', category: 'action', database: 'gitea' }, + { name: 'action_run', category: 'action', database: 'gitea' }, + { name: 'action_run_index', category: 'action', database: 'gitea' }, + { name: 'action_run_job', category: 'action', database: 'gitea' }, + { name: 'action_runner', category: 'action', database: 'gitea' }, + { name: 'action_runner_token', category: 'action', database: 'gitea' }, + { name: 'action_schedule', category: 'action', database: 'gitea' }, + { name: 'action_schedule_spec', category: 'action', database: 'gitea' }, + { name: 'action_task', category: 'action', database: 'gitea' }, + { name: 'action_task_output', category: 'action', database: 'gitea' }, + { name: 'action_task_step', category: 'action', database: 'gitea' }, + { name: 'action_tasks_version', category: 'action', database: 'gitea' }, + { name: 'action_variable', category: 'action', database: 'gitea' }, + + // Auth tables + { name: 'access', category: 'auth', database: 'gitea' }, + { name: 'access_token', category: 'auth', database: 'gitea' }, + { name: 'auth_token', category: 'auth', database: 'gitea' }, + { name: 'collaboration', category: 'auth', database: 'gitea' }, + { name: 'external_login_user', category: 'auth', database: 'gitea' }, + { name: 'login_source', category: 'auth', database: 'gitea' }, + { name: 'oauth2_application', category: 'auth', database: 'gitea' }, + { name: 'oauth2_authorization_code', category: 'auth', database: 'gitea' }, + { name: 'oauth2_grant', category: 'auth', database: 'gitea' }, + { name: 'secret', category: 'auth', database: 'gitea' }, + { name: 'session', category: 'auth', database: 'gitea' }, + + // Core data + { name: 'issue', category: 'core', database: 'gitea' }, + { name: 'issue_assignees', category: 'core', database: 'gitea' }, + { name: 'issue_label', category: 'core', database: 'gitea' }, + { name: 'issue_user', category: 'core', database: 'gitea' }, + { name: 'issue_watch', category: 'core', database: 'gitea' }, + { name: 'pull_request', category: 'core', database: 'gitea' }, + { name: 'comment', category: 'core', database: 'gitea' }, + { name: 'label', category: 'core', database: 'gitea' }, + { name: 'milestone', category: 'core', database: 'gitea' }, + { name: 'project', category: 'core', database: 'gitea' }, + { name: 'project_board', category: 'core', database: 'gitea' }, + { name: 'project_issue', category: 'core', database: 'gitea' }, + { name: 'reaction', category: 'core', database: 'gitea' }, + { name: 'review', category: 'core', database: 'gitea' }, + { name: 'attachment', category: 'core', database: 'gitea' }, + + // Teams/Orgs + { name: 'team', category: 'user', database: 'gitea' }, + { name: 'team_invite', category: 'user', database: 'gitea' }, + { name: 'team_repo', category: 'user', database: 'gitea' }, + { name: 'team_unit', category: 'user', database: 'gitea' }, + { name: 'team_user', category: 'user', database: 'gitea' }, + { name: 'org_user', category: 'user', database: 'gitea' }, + + // System + { name: 'app_state', category: 'system', database: 'gitea' }, + { name: 'badge', category: 'system', database: 'gitea' }, + { name: 'hook_task', category: 'system', database: 'gitea' }, + { name: 'notice', category: 'system', database: 'gitea' }, + { name: 'notification', category: 'system', database: 'gitea' }, + { name: 'package', category: 'system', database: 'gitea' }, + { name: 'system_setting', category: 'system', database: 'gitea' }, + { name: 'task', category: 'system', database: 'gitea' }, + { name: 'version', category: 'system', database: 'gitea' }, + { name: 'webhook', category: 'system', database: 'gitea' }, +]; + +const giteaRelations: TableRelation[] = [ + // Repository -> User (owner) + { from: 'repository', to: 'user', type: 'fk' }, + + // Repository child tables + { from: 'branch', to: 'repository', type: 'fk' }, + { from: 'commit_status', to: 'repository', type: 'fk' }, + { from: 'commit_status_index', to: 'repository', type: 'fk' }, + { from: 'deploy_key', to: 'repository', type: 'fk' }, + { from: 'lfs_lock', to: 'repository', type: 'fk' }, + { from: 'lfs_meta_object', to: 'repository', type: 'fk' }, + { from: 'mirror', to: 'repository', type: 'fk' }, + { from: 'protected_branch', to: 'repository', type: 'fk' }, + { from: 'protected_tag', to: 'repository', type: 'fk' }, + { from: 'push_mirror', to: 'repository', type: 'fk' }, + { from: 'release', to: 'repository', type: 'fk' }, + { from: 'repo_archiver', to: 'repository', type: 'fk' }, + { from: 'repo_indexer_status', to: 'repository', type: 'fk' }, + { from: 'repo_license', to: 'repository', type: 'fk' }, + { from: 'repo_redirect', to: 'repository', type: 'fk' }, + { from: 'repo_topic', to: 'repository', type: 'fk' }, + { from: 'repo_topic', to: 'topic', type: 'fk' }, + { from: 'repo_transfer', to: 'repository', type: 'fk' }, + { from: 'repo_unit', to: 'repository', type: 'fk' }, + { from: 'star', to: 'repository', type: 'fk' }, + { from: 'star', to: 'user', type: 'fk' }, + { from: 'watch', to: 'repository', type: 'fk' }, + { from: 'watch', to: 'user', type: 'fk' }, + { from: 'label', to: 'repository', type: 'fk' }, + { from: 'webhook', to: 'repository', type: 'fk' }, + + // User child tables + { from: 'email_address', to: 'user', type: 'fk' }, + { from: 'email_hash', to: 'user', type: 'fk' }, + { from: 'follow', to: 'user', type: 'fk' }, + { from: 'gpg_key', to: 'user', type: 'fk' }, + { from: 'gpg_key_import', to: 'gpg_key', type: 'fk' }, + { from: 'public_key', to: 'user', type: 'fk' }, + { from: 'two_factor', to: 'user', type: 'fk' }, + { from: 'user_badge', to: 'user', type: 'fk' }, + { from: 'user_badge', to: 'badge', type: 'fk' }, + { from: 'user_blocking', to: 'user', type: 'fk' }, + { from: 'user_open_id', to: 'user', type: 'fk' }, + { from: 'user_redirect', to: 'user', type: 'fk' }, + { from: 'user_setting', to: 'user', type: 'fk' }, + { from: 'webauthn_credential', to: 'user', type: 'fk' }, + + // Actions/CI tables + { from: 'action', to: 'user', type: 'fk' }, + { from: 'action', to: 'repository', type: 'fk' }, + { from: 'action_artifact', to: 'action_run', type: 'fk' }, + { from: 'action_run', to: 'repository', type: 'fk' }, + { from: 'action_run', to: 'user', type: 'fk' }, + { from: 'action_run_index', to: 'repository', type: 'fk' }, + { from: 'action_run_job', to: 'action_run', type: 'fk' }, + { from: 'action_runner', to: 'repository', type: 'ref' }, + { from: 'action_runner_token', to: 'user', type: 'fk' }, + { from: 'action_schedule', to: 'repository', type: 'fk' }, + { from: 'action_schedule_spec', to: 'action_schedule', type: 'fk' }, + { from: 'action_task', to: 'action_run_job', type: 'fk' }, + { from: 'action_task', to: 'action_runner', type: 'fk' }, + { from: 'action_task_output', to: 'action_task', type: 'fk' }, + { from: 'action_task_step', to: 'action_task', type: 'fk' }, + { from: 'action_tasks_version', to: 'action_run', type: 'fk' }, + { from: 'action_variable', to: 'repository', type: 'ref' }, + + // Auth tables + { from: 'access', to: 'user', type: 'fk' }, + { from: 'access', to: 'repository', type: 'fk' }, + { from: 'access_token', to: 'user', type: 'fk' }, + { from: 'auth_token', to: 'user', type: 'fk' }, + { from: 'collaboration', to: 'repository', type: 'fk' }, + { from: 'collaboration', to: 'user', type: 'fk' }, + { from: 'external_login_user', to: 'user', type: 'fk' }, + { from: 'external_login_user', to: 'login_source', type: 'fk' }, + { from: 'oauth2_application', to: 'user', type: 'fk' }, + { from: 'oauth2_authorization_code', to: 'oauth2_grant', type: 'fk' }, + { from: 'oauth2_grant', to: 'oauth2_application', type: 'fk' }, + { from: 'oauth2_grant', to: 'user', type: 'fk' }, + { from: 'secret', to: 'repository', type: 'ref' }, + { from: 'session', to: 'user', type: 'fk' }, + + // Issue/PR tables + { from: 'issue', to: 'repository', type: 'fk' }, + { from: 'issue', to: 'user', type: 'fk' }, + { from: 'issue', to: 'milestone', type: 'fk' }, + { from: 'issue_assignees', to: 'issue', type: 'fk' }, + { from: 'issue_assignees', to: 'user', type: 'fk' }, + { from: 'issue_label', to: 'issue', type: 'fk' }, + { from: 'issue_label', to: 'label', type: 'fk' }, + { from: 'issue_user', to: 'issue', type: 'fk' }, + { from: 'issue_user', to: 'user', type: 'fk' }, + { from: 'issue_watch', to: 'issue', type: 'fk' }, + { from: 'issue_watch', to: 'user', type: 'fk' }, + { from: 'pull_request', to: 'issue', type: 'fk' }, + { from: 'comment', to: 'issue', type: 'fk' }, + { from: 'comment', to: 'user', type: 'fk' }, + { from: 'reaction', to: 'issue', type: 'ref' }, + { from: 'reaction', to: 'user', type: 'fk' }, + { from: 'review', to: 'pull_request', type: 'fk' }, + { from: 'review', to: 'user', type: 'fk' }, + { from: 'attachment', to: 'issue', type: 'ref' }, + { from: 'attachment', to: 'release', type: 'ref' }, + { from: 'milestone', to: 'repository', type: 'fk' }, + + // Project tables + { from: 'project', to: 'repository', type: 'fk' }, + { from: 'project', to: 'user', type: 'fk' }, + { from: 'project_board', to: 'project', type: 'fk' }, + { from: 'project_issue', to: 'project', type: 'fk' }, + { from: 'project_issue', to: 'issue', type: 'fk' }, + + // Team/Org tables + { from: 'team', to: 'user', type: 'fk' }, + { from: 'team_invite', to: 'team', type: 'fk' }, + { from: 'team_invite', to: 'user', type: 'fk' }, + { from: 'team_repo', to: 'team', type: 'fk' }, + { from: 'team_repo', to: 'repository', type: 'fk' }, + { from: 'team_unit', to: 'team', type: 'fk' }, + { from: 'team_user', to: 'team', type: 'fk' }, + { from: 'team_user', to: 'user', type: 'fk' }, + { from: 'org_user', to: 'user', type: 'fk' }, + + // System tables + { from: 'hook_task', to: 'webhook', type: 'fk' }, + { from: 'notification', to: 'user', type: 'fk' }, + { from: 'notification', to: 'repository', type: 'fk' }, + { from: 'notification', to: 'issue', type: 'ref' }, + { from: 'package', to: 'repository', type: 'fk' }, + { from: 'task', to: 'repository', type: 'ref' }, +]; + +// ARCHITECT Database Schema +const architectTables: TableSchema[] = [ + // Agent tables + { name: 'agents', category: 'agent', database: 'architect' }, + { name: 'agent_context_index', category: 'agent', database: 'architect' }, + + // Context tables + { name: 'context_algorithms', category: 'context', database: 'architect' }, + { name: 'context_blocks', category: 'context', database: 'architect' }, + { name: 'context_blocks_history', category: 'context', database: 'architect' }, + { name: 'ambient_context', category: 'context', database: 'architect' }, + { name: 's_contract_contexts', category: 'context', database: 'architect' }, + { name: 's_contract_datasets', category: 'context', database: 'architect' }, + + // Credentials tables + { name: 'creds_architect', category: 'creds', database: 'architect' }, + { name: 'creds_corp', category: 'creds', database: 'architect' }, + { name: 'creds_deck', category: 'creds', database: 'architect' }, + { name: 'creds_hst', category: 'creds', database: 'architect' }, + { name: 'creds_locker', category: 'creds', database: 'architect' }, + { name: 'creds_runpod', category: 'creds', database: 'architect' }, + + // Core data + { name: 'hilos', category: 'core', database: 'architect' }, + { name: 'messages', category: 'core', database: 'architect' }, + { name: 'sessions', category: 'core', database: 'architect' }, + { name: 'memory', category: 'core', database: 'architect' }, + { name: 'knowledge_base', category: 'core', database: 'architect' }, + + // System + { name: 'algorithm_experiments', category: 'system', database: 'architect' }, + { name: 'algorithm_metrics', category: 'system', database: 'architect' }, + { name: 'app_config', category: 'system', database: 'architect' }, + { name: 'audit_violations', category: 'system', database: 'architect' }, + { name: 'immutable_log', category: 'system', database: 'architect' }, + { name: 'indices', category: 'system', database: 'architect' }, +]; + +const architectRelations: TableRelation[] = [ + // Agent relations + { from: 'agent_context_index', to: 'agents', type: 'fk' }, + { from: 'agent_context_index', to: 'context_blocks', type: 'fk' }, + + // Context relations + { from: 'context_blocks', to: 'agents', type: 'ref' }, + { from: 'context_blocks', to: 'context_algorithms', type: 'fk' }, + { from: 'context_blocks_history', to: 'context_blocks', type: 'fk' }, + { from: 'ambient_context', to: 'agents', type: 'ref' }, + { from: 'ambient_context', to: 'sessions', type: 'ref' }, + { from: 's_contract_contexts', to: 's_contract_datasets', type: 'ref' }, + { from: 's_contract_contexts', to: 'agents', type: 'ref' }, + + // Core data relations + { from: 'messages', to: 'sessions', type: 'fk' }, + { from: 'messages', to: 'hilos', type: 'fk' }, + { from: 'messages', to: 'agents', type: 'fk' }, + { from: 'hilos', to: 'sessions', type: 'fk' }, + { from: 'hilos', to: 'agents', type: 'ref' }, + { from: 'sessions', to: 'agents', type: 'fk' }, + { from: 'memory', to: 'agents', type: 'fk' }, + { from: 'memory', to: 'sessions', type: 'ref' }, + { from: 'knowledge_base', to: 'agents', type: 'ref' }, + + // Credentials relations (todas referenciadas por agents) + { from: 'creds_architect', to: 'agents', type: 'ref' }, + { from: 'creds_corp', to: 'agents', type: 'ref' }, + { from: 'creds_deck', to: 'agents', type: 'ref' }, + { from: 'creds_hst', to: 'agents', type: 'ref' }, + { from: 'creds_locker', to: 'agents', type: 'ref' }, + { from: 'creds_runpod', to: 'agents', type: 'ref' }, + + // System relations + { from: 'algorithm_metrics', to: 'algorithm_experiments', type: 'fk' }, + { from: 'algorithm_experiments', to: 'context_algorithms', type: 'ref' }, + { from: 'audit_violations', to: 'agents', type: 'fk' }, + { from: 'audit_violations', to: 'sessions', type: 'fk' }, + { from: 'immutable_log', to: 'agents', type: 'ref' }, + { from: 'immutable_log', to: 'sessions', type: 'ref' }, + { from: 'indices', to: 'knowledge_base', type: 'ref' }, + { from: 'app_config', to: 'agents', type: 'ref' }, +]; + +export const DB_SCHEMAS: Record = { + hst: { tables: hstTables, relations: hstRelations }, + deck: { tables: deckTables, relations: deckRelations }, + gitea: { tables: giteaTables, relations: giteaRelations }, + architect: { tables: architectTables, relations: architectRelations } +}; + +// Category colors +export const CATEGORY_COLORS: Record = { + core: '#7c8aff', + directus: '#9C27B0', + api: '#4CAF50', + graph: '#FF9800', + tree: '#00BCD4', + library: '#E91E63', + data: '#8BC34A', + system: '#607D8B', + repo: '#2196F3', + user: '#FF5722', + action: '#795548', + auth: '#009688', + agent: '#673AB7', + context: '#3F51B5', + creds: '#F44336', + comm: '#00ACC1', + storage: '#FF6F00' +}; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..5ab8f39 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,148 @@ +import { store } from './state/index.ts'; +import { fetchInfrastructure } from './api/index.ts'; +import { DB_SCHEMAS } from './api/schemas.ts'; +import { InfraGraph } from './views/InfraGraph.ts'; +import { TablesGraph } from './views/TablesGraph.ts'; +import './styles/main.css'; + +type ViewType = 'infra' | 'hst' | 'deck' | 'gitea' | 'architect'; + +class App { + private currentView: ViewType = 'infra'; + private infraGraph: InfraGraph | null = null; + private tablesGraph: TablesGraph | null = null; + + async start(): Promise { + const container = document.getElementById('app'); + if (!container) return; + + container.innerHTML = ` +
+
+ + +
+
+
+ + + + +
+
+
+
+ +
+
+
+
+
Cargando...
+
+ `; + + // Load infrastructure data + try { + const data = await fetchInfrastructure(); + store.setState({ + servers: data.servers, + services: data.services, + connections: data.connections, + loading: false + }); + } catch (err) { + store.setState({ error: 'Error cargando datos', loading: false }); + console.error(err); + } + + this.bindEvents(); + this.renderView(); + } + + private async renderView(): Promise { + const graphContainer = document.getElementById('graph-container'); + if (!graphContainer) return; + + // Cleanup previous + this.infraGraph?.unmount(); + this.tablesGraph?.unmount(); + this.infraGraph = null; + this.tablesGraph = null; + + graphContainer.innerHTML = '
Cargando...
'; + + switch (this.currentView) { + case 'infra': + this.infraGraph = new InfraGraph(graphContainer, store); + await this.infraGraph.mount(); + break; + case 'hst': + this.tablesGraph = new TablesGraph(graphContainer, 'hst', DB_SCHEMAS.hst); + await this.tablesGraph.mount(); + break; + case 'deck': + this.tablesGraph = new TablesGraph(graphContainer, 'deck', DB_SCHEMAS.deck); + await this.tablesGraph.mount(); + break; + case 'gitea': + this.tablesGraph = new TablesGraph(graphContainer, 'gitea', DB_SCHEMAS.gitea); + await this.tablesGraph.mount(); + break; + case 'architect': + this.tablesGraph = new TablesGraph(graphContainer, 'architect', DB_SCHEMAS.architect); + await this.tablesGraph.mount(); + break; + } + } + + private bindEvents(): void { + // View tabs + document.querySelectorAll('.view-tab').forEach(tab => { + tab.addEventListener('click', () => { + const view = (tab as HTMLElement).dataset.view as ViewType; + if (!view || view === this.currentView) return; + + document.querySelectorAll('.view-tab').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + + this.currentView = view; + this.renderView(); + }); + }); + + // Refresh button + const refreshBtn = document.getElementById('btn-refresh'); + if (refreshBtn) { + refreshBtn.onclick = async () => { + refreshBtn.textContent = 'Cargando...'; + refreshBtn.setAttribute('disabled', 'true'); + + try { + if (this.currentView === 'infra') { + const data = await fetchInfrastructure(); + store.setState({ + servers: data.servers, + services: data.services, + connections: data.connections + }); + } + await this.renderView(); + } finally { + refreshBtn.textContent = 'Actualizar'; + refreshBtn.removeAttribute('disabled'); + } + }; + } + + // Keyboard shortcuts + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + document.querySelector('#node-detail')?.classList.remove('open'); + } + }); + } +} + +document.addEventListener('DOMContentLoaded', () => { + new App().start(); +}); diff --git a/src/state/index.ts b/src/state/index.ts new file mode 100644 index 0000000..9b526b9 --- /dev/null +++ b/src/state/index.ts @@ -0,0 +1,21 @@ +import { createStore } from './store.ts'; +import type { AppState } from '../types/index.ts'; + +const initialState: AppState = { + servers: [], + services: [], + connections: [], + selectedNode: null, + showServices: true, + showConnections: true, + filterStatus: 'all', + graphSettings: { + nodeSize: 40, + linkDist: 120, + showLabels: true + }, + loading: true, + error: null +}; + +export const store = createStore(initialState); diff --git a/src/state/store.ts b/src/state/store.ts new file mode 100644 index 0000000..865b837 --- /dev/null +++ b/src/state/store.ts @@ -0,0 +1,27 @@ +type Listener = (state: T, prevState: T) => void; + +export interface Store { + getState: () => Readonly; + setState: (partial: Partial) => void; + subscribe: (listener: Listener) => () => void; +} + +export function createStore(initialState: T): Store { + let state = { ...initialState }; + const listeners = new Set>(); + + return { + getState: (): Readonly => state, + + setState: (partial: Partial): void => { + const prevState = state; + state = { ...state, ...partial }; + listeners.forEach(fn => fn(state, prevState)); + }, + + subscribe: (listener: Listener): (() => void) => { + listeners.add(listener); + return () => listeners.delete(listener); + } + }; +} diff --git a/src/styles/main.css b/src/styles/main.css new file mode 100644 index 0000000..d0cbb36 --- /dev/null +++ b/src/styles/main.css @@ -0,0 +1,584 @@ +:root { + --bg: #0f0f0f; + --bg-secondary: #1a1a1a; + --bg-tertiary: #252525; + --text: #e0e0e0; + --text-secondary: #888; + --accent: #7c8aff; + --accent-hover: #9ca6ff; + --border: #333; + --success: #4CAF50; + --warning: #FFC107; + --error: #F44336; + --radius: 8px; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: var(--bg); + color: var(--text); + line-height: 1.5; + overflow: hidden; +} + +/* Topbar */ +.topbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 20px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + height: 56px; +} + +.topbar-left { + display: flex; + align-items: center; + gap: 16px; +} + +.topbar-center { + display: flex; + align-items: center; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 10px; +} + +.logo { + font-size: 18px; + font-weight: 700; + color: var(--accent); + letter-spacing: 2px; +} + +/* View Tabs */ +.view-tabs { + display: flex; + gap: 4px; + background: var(--bg-tertiary); + padding: 4px; + border-radius: var(--radius); +} + +.view-tab { + padding: 8px 16px; + border: none; + background: transparent; + color: var(--text-secondary); + font-size: 13px; + font-weight: 500; + cursor: pointer; + border-radius: 6px; + transition: all 0.2s; +} + +.view-tab:hover { + color: var(--text); + background: rgba(255,255,255,0.05); +} + +.view-tab.active { + background: var(--accent); + color: #fff; +} + +/* Legacy header support */ +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 24px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + height: 60px; +} + +.header-left { + display: flex; + align-items: center; + gap: 16px; +} + +.header-right { + display: flex; + gap: 12px; +} + +/* Buttons */ +.btn { + padding: 8px 16px; + border-radius: var(--radius); + border: none; + background: var(--accent); + color: #fff; + font-size: 14px; + cursor: pointer; + transition: background 0.2s; + text-decoration: none; +} + +.btn:hover { + background: var(--accent-hover); +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-outline { + background: transparent; + border: 1px solid var(--accent); + color: var(--accent); +} + +.btn-outline:hover { + background: var(--accent); + color: #fff; +} + +.btn-sm { + padding: 6px 12px; + font-size: 12px; +} + +/* Main layout */ +.main { + height: calc(100vh - 60px); + position: relative; + overflow: hidden; +} + +/* Loading */ +.loading { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-secondary); + font-size: 16px; +} + +.empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-secondary); +} + +/* Graph sidebar */ +.graph-sidebar { + position: absolute; + top: 16px; + left: 16px; + width: 240px; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px; + z-index: 100; + max-height: calc(100% - 32px); + overflow-y: auto; +} + +.sidebar-header h3 { + font-size: 16px; + margin-bottom: 16px; + color: var(--accent); +} + +.graph-section { + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid var(--border); +} + +.graph-section:last-child { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; +} + +.graph-section-title { + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + margin-bottom: 12px; +} + +.graph-stat { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + font-size: 14px; +} + +.graph-stat-value { + font-weight: 600; + color: var(--accent); +} + +.graph-checkbox { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + font-size: 14px; + cursor: pointer; +} + +.graph-checkbox input { + accent-color: var(--accent); +} + +.graph-select { + margin-top: 12px; +} + +.graph-select label { + display: block; + font-size: 12px; + color: var(--text-secondary); + margin-bottom: 4px; +} + +.graph-select select { + width: 100%; + padding: 8px; + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--bg-tertiary); + color: var(--text); + font-size: 14px; +} + +.graph-slider { + margin-top: 12px; +} + +.graph-slider-label { + display: flex; + justify-content: space-between; + font-size: 12px; + margin-bottom: 4px; +} + +.graph-slider-value { + color: var(--accent); +} + +.graph-slider input[type="range"] { + width: 100%; + accent-color: var(--accent); +} + +/* Graph controls */ +.graph-controls { + position: absolute; + top: 16px; + right: 16px; + display: flex; + gap: 8px; + z-index: 100; +} + +/* Graph legend */ +.graph-legend { + position: absolute; + bottom: 16px; + left: 16px; + display: flex; + gap: 24px; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 12px 16px; + z-index: 100; +} + +.legend-section { + display: flex; + align-items: center; + gap: 12px; +} + +.legend-title { + font-size: 11px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; +} + +.legend-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; +} + +.color-dot { + width: 12px; + height: 12px; + border-radius: 50%; +} + +.color-ring { + width: 12px; + height: 12px; + border-radius: 50%; + border: 3px solid; + background: transparent; +} + +.color-line { + width: 20px; + height: 3px; + border-radius: 2px; +} + +.color-line.dashed { + background: repeating-linear-gradient( + 90deg, + currentColor, + currentColor 4px, + transparent 4px, + transparent 8px + ) !important; +} + +/* Node detail panel */ +.node-detail { + position: absolute; + top: 16px; + right: 16px; + width: 300px; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px; + z-index: 200; + display: none; + margin-top: 48px; +} + +.node-detail.open { + display: block; +} + +.detail-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border); +} + +.detail-header h4 { + font-size: 18px; + color: var(--accent); +} + +.status-badge { + padding: 4px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; +} + +.status-badge.healthy { + background: rgba(76, 175, 80, 0.2); + color: var(--success); +} + +.status-badge.running { + background: rgba(139, 195, 74, 0.2); + color: #8BC34A; +} + +.status-badge.warning { + background: rgba(255, 193, 7, 0.2); + color: var(--warning); +} + +.status-badge.error { + background: rgba(244, 67, 54, 0.2); + color: var(--error); +} + +.status-badge.suspended { + background: rgba(158, 158, 158, 0.2); + color: #9E9E9E; +} + +.detail-row { + margin-bottom: 8px; + font-size: 14px; +} + +.detail-row strong { + color: var(--text-secondary); +} + +.detail-row a { + color: var(--accent); +} + +.detail-section { + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid var(--border); +} + +.detail-section-title { + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + margin-bottom: 8px; +} + +.detail-services { + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid var(--border); +} + +.detail-service { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 0; + font-size: 13px; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.status-dot.healthy { + background: var(--success); +} + +.status-dot.running { + background: #8BC34A; +} + +.status-dot.warning { + background: var(--warning); +} + +.status-dot.error { + background: var(--error); +} + +.uptime { + margin-left: auto; + color: var(--text-secondary); + font-size: 11px; +} + +/* SVG styles */ +svg { + display: block; + background: var(--bg); +} + +.node-label { + pointer-events: none; + user-select: none; +} + +.node-icon { + pointer-events: none; + user-select: none; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg); +} + +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); +} + +/* Columns table in detail panel */ +.columns-table { + max-height: 300px; + overflow-y: auto; + margin-top: 8px; +} + +.column-row { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + font-size: 12px; + border-radius: 4px; + margin-bottom: 2px; +} + +.column-row:hover { + background: var(--bg-tertiary); +} + +.column-row.primary { + background: rgba(124, 138, 255, 0.1); +} + +.column-name { + font-family: monospace; + font-weight: 500; + flex: 1; +} + +.column-type { + color: var(--text-secondary); + font-family: monospace; + font-size: 11px; +} + +.column-not-null { + color: var(--warning); + font-size: 10px; + font-weight: 600; +} + +.loading-small { + color: var(--text-secondary); + font-size: 12px; + padding: 8px 0; +} + +/* Wider detail panel for tables */ +.node-detail { + max-height: calc(100vh - 150px); + overflow-y: auto; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..3b8d78a --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,101 @@ +// Tipos de nodos en el grafo +export type NodeType = 'server' | 'service' | 'database' | 'storage' | 'user' | 'endpoint'; + +// Estado de salud +export type HealthStatus = 'healthy' | 'running' | 'warning' | 'error' | 'suspended'; + +// Servidor +export interface Server { + id: string; + name: string; + ip: string; + description: string; + status: HealthStatus; + suspended?: boolean; +} + +// Servicio +export interface Service { + id: string; + name: string; + serverId: string; + type: NodeType; + status: HealthStatus; + uptime?: string; + port?: number; + url?: string; +} + +// Conexión entre nodos +export interface Connection { + source: string; + target: string; + type: 'ssh' | 'api' | 'sync' | 'depends' | 'http'; + label?: string; +} + +// Nodo del grafo +export interface GraphNode { + id: string; + label: string; + type: NodeType; + status: HealthStatus; + parent?: string; + metadata?: Record; + x?: number; + y?: number; + fx?: number | null; + fy?: number | null; +} + +// Edge del grafo +export interface GraphEdge { + source: string | GraphNode; + target: string | GraphNode; + type: Connection['type']; + label?: string; +} + +// Estado de la aplicación +export interface AppState { + servers: Server[]; + services: Service[]; + connections: Connection[]; + selectedNode: string | null; + showServices: boolean; + showConnections: boolean; + filterStatus: HealthStatus | 'all'; + graphSettings: { + nodeSize: number; + linkDist: number; + showLabels: boolean; + }; + loading: boolean; + error: string | null; +} + +// Configuración de colores +export const NODE_COLORS: Record = { + server: '#7c8aff', + service: '#4CAF50', + database: '#FF9800', + storage: '#00BCD4', + user: '#E91E63', + endpoint: '#9C27B0' +}; + +export const STATUS_COLORS: Record = { + healthy: '#4CAF50', + running: '#8BC34A', + warning: '#FFC107', + error: '#F44336', + suspended: '#9E9E9E' +}; + +export const CONNECTION_COLORS: Record = { + ssh: '#2196F3', + api: '#9C27B0', + sync: '#00BCD4', + depends: '#607D8B', + http: '#FF5722' +}; diff --git a/src/views/InfraGraph.ts b/src/views/InfraGraph.ts new file mode 100644 index 0000000..f74e522 --- /dev/null +++ b/src/views/InfraGraph.ts @@ -0,0 +1,638 @@ +import type { Store } from '../state/store.ts'; +import type { + AppState, + GraphNode, + GraphEdge, + NodeType, + HealthStatus +} from '../types/index.ts'; +import { NODE_COLORS, STATUS_COLORS, CONNECTION_COLORS } from '../types/index.ts'; + +type D3Module = typeof import('d3'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type D3Selection = any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type D3Simulation = any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type D3Zoom = any; + +export class InfraGraph { + private container: HTMLElement; + private store: Store; + private d3: D3Module | null = null; + private simulation: D3Simulation | null = null; + private zoom: D3Zoom | null = null; + private svg: D3Selection | null = null; + private g: D3Selection | null = null; + + constructor(container: HTMLElement, store: Store) { + this.container = container; + this.store = store; + } + + async mount(): Promise { + this.container.innerHTML = '
Cargando infraestructura...
'; + + if (!this.d3) { + this.d3 = await import('d3'); + } + + this.render(); + } + + render(): void { + if (!this.d3) return; + const d3 = this.d3; + const state = this.store.getState(); + + // Build nodes + const nodes: GraphNode[] = []; + const nodeMap = new Map(); + + // Add servers as main nodes + state.servers.forEach(server => { + if (state.filterStatus !== 'all' && server.status !== state.filterStatus) return; + + // Determine node type (user is special) + const nodeType: NodeType = server.id === 'user' ? 'user' : 'server'; + + const node: GraphNode = { + id: server.id, + label: server.name, + type: nodeType, + status: server.status, + metadata: { ip: server.ip, desc: server.description } + }; + nodes.push(node); + nodeMap.set(server.id, node); + }); + + // Add services if enabled + if (state.showServices) { + state.services.forEach(service => { + if (!nodeMap.has(service.serverId)) return; + if (state.filterStatus !== 'all' && service.status !== state.filterStatus) return; + + const node: GraphNode = { + id: service.id, + label: service.name, + type: service.type, + status: service.status, + parent: service.serverId, + metadata: service.uptime ? { uptime: service.uptime } : undefined + }; + nodes.push(node); + nodeMap.set(service.id, node); + }); + } + + // Build edges + const edges: GraphEdge[] = []; + + // Server-to-service edges + if (state.showServices) { + state.services.forEach(service => { + if (nodeMap.has(service.id) && nodeMap.has(service.serverId)) { + edges.push({ + source: service.serverId, + target: service.id, + type: 'depends' + }); + } + }); + } + + // Connection edges + if (state.showConnections) { + state.connections.forEach(conn => { + if (nodeMap.has(conn.source) && nodeMap.has(conn.target)) { + edges.push({ + source: conn.source, + target: conn.target, + type: conn.type, + label: conn.label + }); + } + }); + } + + if (nodes.length === 0) { + this.container.innerHTML = '
Sin nodos para mostrar
'; + return; + } + + // Create container structure + this.container.innerHTML = ` +
+
+ + + +
+
+
+ `; + + this.renderSidebar(nodes.length, edges.length); + this.renderLegend(); + + // Create SVG + const width = this.container.clientWidth; + const height = this.container.clientHeight || 700; + + this.svg = d3.select(this.container) + .append('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', `0 0 ${width} ${height}`); + + // Defs for markers (arrows) + const defs = this.svg.append('defs'); + ['ssh', 'api', 'sync', 'depends', 'http'].forEach(type => { + defs.append('marker') + .attr('id', `arrow-${type}`) + .attr('viewBox', '0 -5 10 10') + .attr('refX', 20) + .attr('refY', 0) + .attr('markerWidth', 6) + .attr('markerHeight', 6) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M0,-5L10,0L0,5') + .attr('fill', CONNECTION_COLORS[type as keyof typeof CONNECTION_COLORS]); + }); + + this.g = this.svg.append('g'); + + // Zoom + this.zoom = d3.zoom() + .scaleExtent([0.2, 3]) + .on('zoom', (event: { transform: string }) => { + this.g.attr('transform', event.transform); + }); + + this.svg.call(this.zoom); + this.bindZoomControls(); + + // Force simulation + const { nodeSize, linkDist } = state.graphSettings; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.simulation = d3.forceSimulation(nodes as any) + .force('link', d3.forceLink(edges) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .id((d: any) => d.id) + .distance((d: GraphEdge) => { + // Shorter distance for server-service links + const source = d.source as GraphNode; + const target = d.target as GraphNode; + if (source.type === 'server' || target.type === 'server') { + return linkDist * 0.6; + } + return linkDist; + })) + .force('charge', d3.forceManyBody().strength(-300)) + .force('center', d3.forceCenter(width / 2, height / 2)) + .force('collision', d3.forceCollide().radius((d: GraphNode) => + d.type === 'server' || d.type === 'user' ? nodeSize * 1.5 : nodeSize + 5 + )) + .force('x', d3.forceX(width / 2).strength(0.05)) + .force('y', d3.forceY(height / 2).strength(0.05)); + + // Links + const link = this.g.append('g') + .attr('class', 'links') + .selectAll('line') + .data(edges) + .join('line') + .attr('stroke', (d: GraphEdge) => CONNECTION_COLORS[d.type] || '#999') + .attr('stroke-width', (d: GraphEdge) => d.type === 'ssh' ? 2.5 : 1.5) + .attr('stroke-opacity', 0.7) + .attr('stroke-dasharray', (d: GraphEdge) => d.type === 'sync' ? '5,5' : null) + .attr('marker-end', (d: GraphEdge) => d.type !== 'depends' ? `url(#arrow-${d.type})` : null); + + // Nodes + const node = this.g.append('g') + .attr('class', 'nodes') + .selectAll('g') + .data(nodes) + .join('g') + .attr('cursor', 'pointer') + .call(this.createDrag(d3, this.simulation)); + + // Node circles with status ring + node.append('circle') + .attr('r', (d: GraphNode) => { + if (d.type === 'server' || d.type === 'user') return nodeSize * 1.2; + if (d.type === 'endpoint') return nodeSize * 0.6; + return nodeSize * 0.7; + }) + .attr('fill', (d: GraphNode) => NODE_COLORS[d.type]) + .attr('stroke', (d: GraphNode) => STATUS_COLORS[d.status]) + .attr('stroke-width', 3) + .attr('opacity', (d: GraphNode) => d.status === 'suspended' ? 0.4 : 1); + + // Node icons/text + node.append('text') + .attr('class', 'node-icon') + .attr('text-anchor', 'middle') + .attr('dy', '0.35em') + .attr('font-size', (d: GraphNode) => { + if (d.type === 'server' || d.type === 'user') return 16; + if (d.type === 'endpoint') return 10; + return 12; + }) + .attr('fill', '#fff') + .text((d: GraphNode) => this.getNodeIcon(d.type)); + + // Labels + if (state.graphSettings.showLabels) { + node.append('text') + .attr('class', 'node-label') + .attr('text-anchor', 'middle') + .attr('dy', (d: GraphNode) => { + if (d.type === 'server' || d.type === 'user') return nodeSize * 1.5 + 10; + if (d.type === 'endpoint') return nodeSize * 0.8 + 5; + return nodeSize + 5; + }) + .attr('font-size', (d: GraphNode) => { + if (d.type === 'server' || d.type === 'user') return 12; + if (d.type === 'endpoint') return 8; + return 10; + }) + .attr('fill', 'var(--text)') + .attr('font-weight', (d: GraphNode) => d.type === 'server' || d.type === 'user' ? 'bold' : 'normal') + .text((d: GraphNode) => d.label); + } + + // Click handler + node.on('click', (_: MouseEvent, d: GraphNode) => { + this.store.setState({ selectedNode: d.id }); + this.showNodeDetail(d); + }); + + // Hover effects + node.on('mouseenter', function() { + d3.select(this).select('circle') + .transition().duration(150) + .attr('transform', 'scale(1.1)'); + }); + node.on('mouseleave', function() { + d3.select(this).select('circle') + .transition().duration(150) + .attr('transform', 'scale(1)'); + }); + + // Tick + this.simulation.on('tick', () => { + link + .attr('x1', (d: GraphEdge) => (d.source as GraphNode).x!) + .attr('y1', (d: GraphEdge) => (d.source as GraphNode).y!) + .attr('x2', (d: GraphEdge) => (d.target as GraphNode).x!) + .attr('y2', (d: GraphEdge) => (d.target as GraphNode).y!); + + node.attr('transform', (d: GraphNode) => `translate(${d.x},${d.y})`); + }); + } + + private getNodeIcon(type: NodeType): string { + switch (type) { + case 'server': return '\u2b22'; // Hexagon + case 'database': return '\u25a0'; // Square + case 'storage': return '\u25c6'; // Diamond + case 'service': return '\u25cf'; // Circle + case 'user': return '\u263a'; // Smiley + case 'endpoint': return '\u25c9'; // Fisheye + default: return '\u25cf'; + } + } + + private renderSidebar(nodeCount: number, edgeCount: number): void { + const sidebar = this.container.querySelector('#graph-sidebar'); + if (!sidebar) return; + + const state = this.store.getState(); + const { graphSettings, showServices, showConnections, filterStatus } = state; + + const serverCount = state.servers.filter(s => !s.suspended).length; + const serviceCount = state.services.length; + + sidebar.innerHTML = ` + + +
+
+ Servidores + ${serverCount} +
+
+ Servicios + ${serviceCount} +
+
+ Nodos visibles + ${nodeCount} +
+
+ Conexiones + ${edgeCount} +
+
+ +
+
Filtros
+ + +
+ + +
+
+ +
+
Visualizacion
+ +
+
+ Nodo + ${graphSettings.nodeSize}px +
+ +
+
+
+ Distancia + ${graphSettings.linkDist}px +
+ +
+
+ `; + + this.bindSidebarEvents(sidebar as HTMLElement); + } + + private bindSidebarEvents(sidebar: HTMLElement): void { + // Show services + const showSvc = sidebar.querySelector('#show-services'); + if (showSvc) { + showSvc.onchange = () => { + this.store.setState({ showServices: showSvc.checked }); + this.render(); + }; + } + + // Show connections + const showConn = sidebar.querySelector('#show-connections'); + if (showConn) { + showConn.onchange = () => { + this.store.setState({ showConnections: showConn.checked }); + this.render(); + }; + } + + // Filter status + const filterSel = sidebar.querySelector('#filter-status'); + if (filterSel) { + filterSel.onchange = () => { + this.store.setState({ filterStatus: filterSel.value as HealthStatus | 'all' }); + this.render(); + }; + } + + // Show labels + const showLbl = sidebar.querySelector('#show-labels'); + if (showLbl) { + showLbl.onchange = () => { + const state = this.store.getState(); + this.store.setState({ + graphSettings: { ...state.graphSettings, showLabels: showLbl.checked } + }); + this.render(); + }; + } + + // Node size + const nodeSize = sidebar.querySelector('#graph-node-size'); + const nodeSizeVal = sidebar.querySelector('#node-size-val'); + if (nodeSize) { + nodeSize.oninput = () => { + const size = parseInt(nodeSize.value, 10); + if (nodeSizeVal) nodeSizeVal.textContent = `${size}px`; + const state = this.store.getState(); + this.store.setState({ + graphSettings: { ...state.graphSettings, nodeSize: size } + }); + this.render(); + }; + } + + // Link distance + const linkDist = sidebar.querySelector('#graph-link-dist'); + const linkDistVal = sidebar.querySelector('#link-dist-val'); + if (linkDist) { + linkDist.oninput = () => { + const dist = parseInt(linkDist.value, 10); + if (linkDistVal) linkDistVal.textContent = `${dist}px`; + const state = this.store.getState(); + this.store.setState({ + graphSettings: { ...state.graphSettings, linkDist: dist } + }); + if (this.simulation) { + this.simulation.force('link').distance(dist); + this.simulation.alpha(0.3).restart(); + } + }; + } + } + + private renderLegend(): void { + const legend = this.container.querySelector('#graph-legend'); + if (!legend) return; + + legend.innerHTML = ` +
+ Tipo: +
+ + User +
+
+ + Endpoint +
+
+ + Server +
+
+ + Service +
+
+ + DB +
+
+ + Storage +
+
+
+ Conexion: +
+ + HTTP +
+
+ + SSH +
+
+ + API +
+
+ + Sync +
+
+ `; + } + + private showNodeDetail(node: GraphNode): void { + const panel = this.container.querySelector('#node-detail'); + if (!panel) return; + + const state = this.store.getState(); + let details = ''; + + if (node.type === 'server') { + const server = state.servers.find(s => s.id === node.id); + if (server) { + const services = state.services.filter(s => s.serverId === server.id); + details = ` +
+

${server.name}

+ ${server.status} +
+
IP: ${server.ip}
+
Descripcion: ${server.description}
+
Servicios: ${services.length}
+
+ ${services.map(s => ` +
+ + ${s.name} + ${s.uptime ? `${s.uptime}` : ''} +
+ `).join('')} +
+ `; + } + } else { + const service = state.services.find(s => s.id === node.id); + if (service) { + const server = state.servers.find(s => s.id === service.serverId); + details = ` +
+

${service.name}

+ ${service.status} +
+
Servidor: ${server?.name || service.serverId}
+
Tipo: ${service.type}
+ ${service.uptime ? `
Uptime: ${service.uptime}
` : ''} + ${service.url ? `` : ''} + `; + } + } + + panel.innerHTML = details; + panel.classList.add('open'); + + // Close on click outside + const closeHandler = (e: MouseEvent) => { + if (!panel.contains(e.target as Node) && !(e.target as HTMLElement).closest('.nodes')) { + panel.classList.remove('open'); + document.removeEventListener('click', closeHandler); + } + }; + setTimeout(() => document.addEventListener('click', closeHandler), 100); + } + + private bindZoomControls(): void { + if (!this.d3 || !this.svg || !this.zoom) return; + const d3 = this.d3; + + const fitBtn = this.container.querySelector('#graph-fit'); + if (fitBtn) { + fitBtn.onclick = () => { + this.svg.transition().duration(300).call( + this.zoom.transform, + d3.zoomIdentity.translate(0, 0).scale(1) + ); + }; + } + + const zinBtn = this.container.querySelector('#graph-zin'); + if (zinBtn) { + zinBtn.onclick = () => { + this.svg.transition().duration(200).call(this.zoom.scaleBy, 1.5); + }; + } + + const zoutBtn = this.container.querySelector('#graph-zout'); + if (zoutBtn) { + zoutBtn.onclick = () => { + this.svg.transition().duration(200).call(this.zoom.scaleBy, 0.67); + }; + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private createDrag(d3: D3Module, simulation: D3Simulation): any { + return d3.drag() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .on('start', (event: any, d: any) => { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .on('drag', (event: any, d: any) => { + d.fx = event.x; + d.fy = event.y; + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .on('end', (event: any, d: any) => { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + }); + } + + unmount(): void { + this.simulation?.stop(); + this.svg = null; + this.g = null; + this.zoom = null; + this.container.innerHTML = ''; + } +} diff --git a/src/views/TablesGraph.ts b/src/views/TablesGraph.ts new file mode 100644 index 0000000..e836cd2 --- /dev/null +++ b/src/views/TablesGraph.ts @@ -0,0 +1,539 @@ +import type { DBSchema, TableSchema, TableRelation } from '../api/schemas.ts'; +import { CATEGORY_COLORS, DB_SCHEMAS } from '../api/schemas.ts'; +import { fetchColumns, fetchTables, fetchTableRelations, type Column } from '../api/infrastructure.ts'; + +type D3Module = typeof import('d3'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type D3Selection = any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type D3Simulation = any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type D3Zoom = any; + +interface TableNode extends TableSchema { + id: string; + x?: number; + y?: number; + fx?: number | null; + fy?: number | null; +} + +interface TableEdge { + source: string | TableNode; + target: string | TableNode; + type: TableRelation['type']; +} + +export class TablesGraph { + private container: HTMLElement; + private serverName: string; + private schema: DBSchema; + private d3: D3Module | null = null; + private simulation: D3Simulation | null = null; + private zoom: D3Zoom | null = null; + private svg: D3Selection | null = null; + private g: D3Selection | null = null; + private showCategories: Set; + private showRelations = true; + private nodeSize = 35; + private linkDist = 100; + + constructor(container: HTMLElement, serverName: string, schema: DBSchema) { + this.container = container; + this.serverName = serverName; + this.schema = schema; + this.showCategories = new Set(['core', 'api', 'graph', 'tree', 'library', 'data', 'system', 'directus', 'comm', 'repo', 'user', 'action', 'auth', 'agent', 'context', 'creds']); + } + + async mount(): Promise { + this.container.innerHTML = '
Cargando esquema...
'; + + if (!this.d3) { + this.d3 = await import('d3'); + } + + // Try to load tables from API, fallback to static data + try { + const [apiTables, apiRelations] = await Promise.all([ + fetchTables(this.serverName), + fetchTableRelations(this.serverName) + ]); + + if (apiTables.length > 0) { + this.schema = { + tables: apiTables.map(t => ({ + name: t.name, + category: t.category as TableSchema['category'], + database: t.database + })), + relations: apiRelations.map(r => ({ + from: r.from, + to: r.to, + type: r.type + })) + }; + } + } catch (e) { + console.log('Using static schema data:', e); + // Keep using the static schema passed in constructor + } + + this.render(); + } + + render(): void { + if (!this.d3) return; + const d3 = this.d3; + + // Filter tables by category + const tables = this.schema.tables.filter(t => this.showCategories.has(t.category)); + const tableNames = new Set(tables.map(t => t.name)); + + // Build nodes + const nodes: TableNode[] = tables.map(t => ({ + ...t, + id: t.name + })); + + // Build edges (only between visible tables, if relations are enabled) + const edges: TableEdge[] = this.showRelations + ? this.schema.relations + .filter(r => tableNames.has(r.from) && tableNames.has(r.to)) + .map(r => ({ + source: r.from, + target: r.to, + type: r.type + })) + : []; + + if (nodes.length === 0) { + this.container.innerHTML = '
Sin tablas para mostrar
'; + return; + } + + // Create container structure + this.container.innerHTML = ` +
+
+ + + +
+
+
+ `; + + this.renderSidebar(nodes.length, edges.length); + this.renderLegend(); + + // Create SVG + const width = this.container.clientWidth; + const height = this.container.clientHeight || 700; + + this.svg = d3.select(this.container) + .append('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', `0 0 ${width} ${height}`); + + // Defs for markers + const defs = this.svg.append('defs'); + ['fk', 'ref', 'logical'].forEach(type => { + const colors: Record = { fk: '#2196F3', ref: '#FF9800', logical: '#9E9E9E' }; + defs.append('marker') + .attr('id', `arrow-${type}`) + .attr('viewBox', '0 -5 10 10') + .attr('refX', 20) + .attr('refY', 0) + .attr('markerWidth', 5) + .attr('markerHeight', 5) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M0,-5L10,0L0,5') + .attr('fill', colors[type]); + }); + + this.g = this.svg.append('g'); + + // Zoom + this.zoom = d3.zoom() + .scaleExtent([0.2, 3]) + .on('zoom', (event: { transform: string }) => { + this.g.attr('transform', event.transform); + }); + + this.svg.call(this.zoom); + this.bindZoomControls(); + + // Force simulation + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.simulation = d3.forceSimulation(nodes as any) + .force('link', d3.forceLink(edges) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .id((d: any) => d.id) + .distance(this.linkDist)) + .force('charge', d3.forceManyBody().strength(-200)) + .force('center', d3.forceCenter(width / 2, height / 2)) + .force('collision', d3.forceCollide().radius(this.nodeSize + 10)) + .force('x', d3.forceX(width / 2).strength(0.03)) + .force('y', d3.forceY(height / 2).strength(0.03)); + + // Links + const link = this.g.append('g') + .attr('class', 'links') + .selectAll('line') + .data(edges) + .join('line') + .attr('stroke', (d: TableEdge) => { + const colors: Record = { fk: '#2196F3', ref: '#FF9800', logical: '#9E9E9E' }; + return colors[d.type] || '#999'; + }) + .attr('stroke-width', (d: TableEdge) => d.type === 'fk' ? 2 : 1.5) + .attr('stroke-opacity', 0.6) + .attr('stroke-dasharray', (d: TableEdge) => d.type === 'logical' ? '4,4' : null) + .attr('marker-end', (d: TableEdge) => `url(#arrow-${d.type})`); + + // Nodes + const node = this.g.append('g') + .attr('class', 'nodes') + .selectAll('g') + .data(nodes) + .join('g') + .attr('cursor', 'pointer') + .call(this.createDrag(d3, this.simulation)); + + // Node rectangles (tables look better as rectangles) + node.append('rect') + .attr('width', this.nodeSize * 2) + .attr('height', this.nodeSize) + .attr('x', -this.nodeSize) + .attr('y', -this.nodeSize / 2) + .attr('rx', 4) + .attr('fill', (d: TableNode) => CATEGORY_COLORS[d.category]) + .attr('stroke', '#fff') + .attr('stroke-width', 1.5); + + // Labels + node.append('text') + .attr('class', 'node-label') + .attr('text-anchor', 'middle') + .attr('dy', '0.35em') + .attr('font-size', 9) + .attr('fill', '#fff') + .attr('font-weight', 500) + .text((d: TableNode) => d.name.length > 12 ? d.name.slice(0, 11) + '...' : d.name); + + // Click handler + node.on('click', (_: MouseEvent, d: TableNode) => { + this.showNodeDetail(d); + }); + + // Hover effects + node.on('mouseenter', function() { + d3.select(this).select('rect') + .transition().duration(150) + .attr('stroke-width', 3); + }); + node.on('mouseleave', function() { + d3.select(this).select('rect') + .transition().duration(150) + .attr('stroke-width', 1.5); + }); + + // Tick + this.simulation.on('tick', () => { + link + .attr('x1', (d: TableEdge) => (d.source as TableNode).x!) + .attr('y1', (d: TableEdge) => (d.source as TableNode).y!) + .attr('x2', (d: TableEdge) => (d.target as TableNode).x!) + .attr('y2', (d: TableEdge) => (d.target as TableNode).y!); + + node.attr('transform', (d: TableNode) => `translate(${d.x},${d.y})`); + }); + } + + private renderSidebar(nodeCount: number, edgeCount: number): void { + const sidebar = this.container.querySelector('#graph-sidebar'); + if (!sidebar) return; + + const categories: TableSchema['category'][] = ['core', 'api', 'graph', 'tree', 'library', 'data', 'directus', 'system', 'comm', 'repo', 'user', 'action', 'auth', 'agent', 'context', 'creds']; + + sidebar.innerHTML = ` + + +
+
+ Tablas + ${nodeCount} +
+
+ Relaciones + ${edgeCount} +
+
+ +
+
Categorias
+ ${categories.map(cat => { + const count = this.schema.tables.filter(t => t.category === cat).length; + if (count === 0) return ''; + return ` + + `; + }).join('')} +
+ +
+
Relaciones
+ +
+ + FK (Foreign Key) +
+
+ + Ref (Reference) +
+
+ + Logical +
+
+ +
+
Visualizacion
+
+
+ Nodo + ${this.nodeSize}px +
+ +
+
+
+ Distancia + ${this.linkDist}px +
+ +
+
+ `; + + this.bindSidebarEvents(sidebar as HTMLElement); + } + + private bindSidebarEvents(sidebar: HTMLElement): void { + // Category checkboxes + sidebar.querySelectorAll('[data-cat]').forEach(cb => { + cb.onchange = () => { + const cat = cb.dataset.cat as TableSchema['category']; + if (cb.checked) { + this.showCategories.add(cat); + } else { + this.showCategories.delete(cat); + } + this.render(); + }; + }); + + // Toggle relations + const toggleRelations = sidebar.querySelector('#toggle-relations'); + if (toggleRelations) { + toggleRelations.onchange = () => { + this.showRelations = toggleRelations.checked; + this.render(); + }; + } + + // Node size + const nodeSize = sidebar.querySelector('#graph-node-size'); + const nodeSizeVal = sidebar.querySelector('#node-size-val'); + if (nodeSize) { + nodeSize.oninput = () => { + this.nodeSize = parseInt(nodeSize.value, 10); + if (nodeSizeVal) nodeSizeVal.textContent = `${this.nodeSize}px`; + this.render(); + }; + } + + // Link distance + const linkDist = sidebar.querySelector('#graph-link-dist'); + const linkDistVal = sidebar.querySelector('#link-dist-val'); + if (linkDist) { + linkDist.oninput = () => { + this.linkDist = parseInt(linkDist.value, 10); + if (linkDistVal) linkDistVal.textContent = `${this.linkDist}px`; + if (this.simulation) { + this.simulation.force('link').distance(this.linkDist); + this.simulation.alpha(0.3).restart(); + } + }; + } + } + + private renderLegend(): void { + const legend = this.container.querySelector('#graph-legend'); + if (!legend) return; + + const visibleCats = Array.from(this.showCategories); + legend.innerHTML = visibleCats.map(cat => ` +
+ + ${cat} +
+ `).join(''); + } + + private async showNodeDetail(node: TableNode): Promise { + const panel = this.container.querySelector('#node-detail'); + if (!panel) return; + + // Find relations + const incoming = this.schema.relations.filter(r => r.to === node.name); + const outgoing = this.schema.relations.filter(r => r.from === node.name); + + // Show loading state first + panel.innerHTML = ` +
+

${node.name}

+ ${node.category} +
+
Database: ${node.database}
+
Server: ${this.serverName.toUpperCase()}
+
+
Campos
+
Cargando...
+
+ `; + panel.classList.add('open'); + + // Fetch columns from API + let columns: Column[] = []; + try { + columns = await fetchColumns(this.serverName, node.name); + } catch (e) { + console.error('Error fetching columns:', e); + } + + // Render full panel with columns + panel.innerHTML = ` +
+

${node.name}

+ ${node.category} +
+
Database: ${node.database}
+
Server: ${this.serverName.toUpperCase()}
+ ${columns.length > 0 ? ` +
+
Campos (${columns.length})
+
+ ${columns.map(c => ` +
+ ${c.primary ? '🔑 ' : ''}${c.name} + ${this.formatDataType(c.dataType)} + ${!c.nullable ? 'NOT NULL' : ''} +
+ `).join('')} +
+
+ ` : '
Sin información de campos
'} + ${incoming.length > 0 ? ` +
+
Referencias entrantes (${incoming.length})
+ ${incoming.map(r => `
${r.from} (${r.type})
`).join('')} +
+ ` : ''} + ${outgoing.length > 0 ? ` +
+
Referencias salientes (${outgoing.length})
+ ${outgoing.map(r => `
${r.to} (${r.type})
`).join('')} +
+ ` : ''} + `; + + const closeHandler = (e: MouseEvent) => { + if (!panel.contains(e.target as Node) && !(e.target as HTMLElement).closest('.nodes')) { + panel.classList.remove('open'); + document.removeEventListener('click', closeHandler); + } + }; + setTimeout(() => document.addEventListener('click', closeHandler), 100); + } + + private formatDataType(type: string): string { + // Shorten common PostgreSQL types + return type + .replace('character varying', 'varchar') + .replace('timestamp with time zone', 'timestamptz') + .replace('timestamp without time zone', 'timestamp') + .replace('double precision', 'float8'); + } + + private bindZoomControls(): void { + if (!this.d3 || !this.svg || !this.zoom) return; + const d3 = this.d3; + + const fitBtn = this.container.querySelector('#graph-fit'); + if (fitBtn) { + fitBtn.onclick = () => { + this.svg.transition().duration(300).call( + this.zoom.transform, + d3.zoomIdentity.translate(0, 0).scale(1) + ); + }; + } + + const zinBtn = this.container.querySelector('#graph-zin'); + if (zinBtn) { + zinBtn.onclick = () => { + this.svg.transition().duration(200).call(this.zoom.scaleBy, 1.5); + }; + } + + const zoutBtn = this.container.querySelector('#graph-zout'); + if (zoutBtn) { + zoutBtn.onclick = () => { + this.svg.transition().duration(200).call(this.zoom.scaleBy, 0.67); + }; + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private createDrag(d3: D3Module, simulation: D3Simulation): any { + return d3.drag() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .on('start', (event: any, d: any) => { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .on('drag', (event: any, d: any) => { + d.fx = event.x; + d.fy = event.y; + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .on('end', (event: any, d: any) => { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + }); + } + + unmount(): void { + this.simulation?.stop(); + this.svg = null; + this.g = null; + this.zoom = null; + this.container.innerHTML = ''; + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..307c17a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "moduleResolution": "bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "isolatedModules": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..5d99d25 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +export default defineConfig({ + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + server: { + port: 5174, + host: true + }, + build: { + outDir: 'dist', + sourcemap: true + } +});