perf:gui相关文件

This commit is contained in:
qi4l
2026-04-30 13:36:36 +08:00
parent 7aa5d1475a
commit 3043a43b5b
22 changed files with 1899 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "jyso-web",
"dependencies": {
"axios": "^1.6.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0",
},
"devDependencies": {
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
"vite": "^5.4.0",
},
},
},
"packages": {
"@babel/code-frame": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@babel/compat-data": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
"@babel/core": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
"@babel/generator": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
"@babel/helper-compilation-targets": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
"@babel/helper-globals": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
"@babel/helper-module-imports": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
"@babel/helper-module-transforms": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
"@babel/helper-plugin-utils": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
"@babel/helper-string-parser": ["@babel/[email protected]", "http://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/[email protected]", "http://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-validator-option": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/helpers": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
"@babel/parser": ["@babel/[email protected]", "http://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
"@babel/plugin-transform-react-jsx-self": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
"@babel/plugin-transform-react-jsx-source": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
"@babel/template": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
"@babel/traverse": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
"@babel/types": ["@babel/[email protected]", "http://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
"@esbuild/android-arm": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
"@esbuild/android-arm64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
"@esbuild/android-x64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
"@esbuild/darwin-arm64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
"@esbuild/darwin-x64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
"@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
"@esbuild/freebsd-x64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
"@esbuild/linux-arm": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
"@esbuild/linux-arm64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
"@esbuild/linux-ia32": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
"@esbuild/linux-loong64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
"@esbuild/linux-mips64el": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
"@esbuild/linux-ppc64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
"@esbuild/linux-riscv64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
"@esbuild/linux-s390x": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
"@esbuild/linux-x64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
"@esbuild/netbsd-x64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
"@esbuild/openbsd-x64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
"@esbuild/sunos-x64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
"@esbuild/win32-arm64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
"@esbuild/win32-ia32": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
"@esbuild/win32-x64": ["@esbuild/[email protected]", "http://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
"@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/[email protected]", "http://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@remix-run/router": ["@remix-run/[email protected]", "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.2.tgz", {}, "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w=="],
"@rolldown/pluginutils": ["@rolldown/[email protected]", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", { "os": "android", "cpu": "arm" }, "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw=="],
"@rollup/rollup-android-arm64": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", { "os": "android", "cpu": "arm64" }, "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA=="],
"@rollup/rollup-darwin-x64": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw=="],
"@rollup/rollup-freebsd-x64": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", { "os": "linux", "cpu": "arm" }, "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", { "os": "linux", "cpu": "arm" }, "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw=="],
"@rollup/rollup-openbsd-x64": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected]", "http://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA=="],
"@types/babel__core": ["@types/[email protected]", "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/[email protected]", "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
"@types/babel__template": ["@types/[email protected]", "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
"@types/babel__traverse": ["@types/[email protected]", "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/estree": ["@types/[email protected]", "http://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/prop-types": ["@types/[email protected]", "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
"@types/react": ["@types/[email protected]", "https://registry.npmmirror.com/@types/react/-/react-18.3.28.tgz", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="],
"@types/react-dom": ["@types/[email protected]", "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
"@vitejs/plugin-react": ["@vitejs/[email protected]", "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
"asynckit": ["[email protected]", "http://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"axios": ["[email protected]", "http://registry.npmmirror.com/axios/-/axios-1.15.2.tgz", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A=="],
"baseline-browser-mapping": ["[email protected]", "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA=="],
"browserslist": ["[email protected]", "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
"call-bind-apply-helpers": ["[email protected]", "http://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"caniuse-lite": ["[email protected]", "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="],
"combined-stream": ["[email protected]", "http://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"convert-source-map": ["[email protected]", "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"csstype": ["[email protected]", "http://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"debug": ["[email protected]", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"delayed-stream": ["[email protected]", "http://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"dunder-proto": ["[email protected]", "http://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"electron-to-chromium": ["[email protected]", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.345.tgz", {}, "sha512-F9JXQGiMrz6yVNPI2qOVPvB9HzjH5cGzhs8oJ6A28V5L/YnzN/0KsuiibqF+F1Fd9qxFzD1BUnYSd8JfULxTwg=="],
"es-define-property": ["[email protected]", "http://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["[email protected]", "http://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["[email protected]", "http://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["[email protected]", "http://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"esbuild": ["[email protected]", "http://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", { "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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
"escalade": ["[email protected]", "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"follow-redirects": ["[email protected]", "http://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
"form-data": ["[email protected]", "http://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"fsevents": ["[email protected]", "http://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["[email protected]", "http://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"gensync": ["[email protected]", "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"get-intrinsic": ["[email protected]", "http://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["[email protected]", "http://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"gopd": ["[email protected]", "http://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"has-symbols": ["[email protected]", "http://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["[email protected]", "http://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasown": ["[email protected]", "http://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="],
"js-tokens": ["[email protected]", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jsesc": ["[email protected]", "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json5": ["[email protected]", "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"loose-envify": ["[email protected]", "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lru-cache": ["[email protected]", "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"math-intrinsics": ["[email protected]", "http://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"mime-db": ["[email protected]", "http://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["[email protected]", "http://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"ms": ["[email protected]", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["[email protected]", "http://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"node-releases": ["[email protected]", "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.38.tgz", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="],
"picocolors": ["[email protected]", "http://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"postcss": ["[email protected]", "http://registry.npmmirror.com/postcss/-/postcss-8.5.12.tgz", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="],
"proxy-from-env": ["[email protected]", "http://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
"react": ["[email protected]", "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
"react-dom": ["[email protected]", "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
"react-refresh": ["[email protected]", "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
"react-router": ["[email protected]", "https://registry.npmmirror.com/react-router/-/react-router-6.30.3.tgz", { "dependencies": { "@remix-run/router": "1.23.2" }, "peerDependencies": { "react": ">=16.8" } }, "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw=="],
"react-router-dom": ["[email protected]", "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.30.3.tgz", { "dependencies": { "@remix-run/router": "1.23.2", "react-router": "6.30.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag=="],
"rollup": ["[email protected]", "http://registry.npmmirror.com/rollup/-/rollup-4.60.2.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.2", "@rollup/rollup-android-arm64": "4.60.2", "@rollup/rollup-darwin-arm64": "4.60.2", "@rollup/rollup-darwin-x64": "4.60.2", "@rollup/rollup-freebsd-arm64": "4.60.2", "@rollup/rollup-freebsd-x64": "4.60.2", "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", "@rollup/rollup-linux-arm-musleabihf": "4.60.2", "@rollup/rollup-linux-arm64-gnu": "4.60.2", "@rollup/rollup-linux-arm64-musl": "4.60.2", "@rollup/rollup-linux-loong64-gnu": "4.60.2", "@rollup/rollup-linux-loong64-musl": "4.60.2", "@rollup/rollup-linux-ppc64-gnu": "4.60.2", "@rollup/rollup-linux-ppc64-musl": "4.60.2", "@rollup/rollup-linux-riscv64-gnu": "4.60.2", "@rollup/rollup-linux-riscv64-musl": "4.60.2", "@rollup/rollup-linux-s390x-gnu": "4.60.2", "@rollup/rollup-linux-x64-gnu": "4.60.2", "@rollup/rollup-linux-x64-musl": "4.60.2", "@rollup/rollup-openbsd-x64": "4.60.2", "@rollup/rollup-openharmony-arm64": "4.60.2", "@rollup/rollup-win32-arm64-msvc": "4.60.2", "@rollup/rollup-win32-ia32-msvc": "4.60.2", "@rollup/rollup-win32-x64-gnu": "4.60.2", "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ=="],
"scheduler": ["[email protected]", "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"semver": ["[email protected]", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"source-map-js": ["[email protected]", "http://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"update-browserslist-db": ["[email protected]", "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
"vite": ["[email protected]", "http://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "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" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
"yallist": ["[email protected]", "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
}
}
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JYso</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
+24
View File
@@ -0,0 +1,24 @@
{
"name": "jyso-web",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.6.0",
"liquid-glass-react": "^1.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0"
},
"devDependencies": {
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
"vite": "^5.4.0"
}
}
+18
View File
@@ -0,0 +1,18 @@
import { Routes, Route, Navigate } from 'react-router-dom'
import Login from './pages/Login'
import Dashboard from './pages/Dashboard'
import ProtectedRoute from './components/ProtectedRoute'
export default function App() {
return (
<Routes>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
</Routes>
)
}
+63
View File
@@ -0,0 +1,63 @@
import axios from 'axios'
let authToken = null
export function setAuthToken(token) {
authToken = token
}
export function getAuthToken() {
return authToken
}
const api = axios.create({
baseURL: '/api',
timeout: 10000,
})
api.interceptors.request.use(config => {
if (authToken) {
config.headers.Authorization = 'Bearer ' + authToken
}
return config
})
api.interceptors.response.use(
response => response,
error => {
if (error.response && error.response.status === 401) {
authToken = null
}
return Promise.reject(error)
}
)
export function login(username, password) {
return axios.post('/api/auth/login', { username, password })
}
export function getStatus() {
return api.get('/status')
}
export function startServers(data) {
return api.post('/servers/start', data)
}
export function getGadgets() {
return api.get('/gadgets')
}
export function generatePayload(data) {
return api.post('/payload/generate', data)
}
export function updateConfig(data) {
return api.post('/config/update', data)
}
export function getConfig() {
return api.get('/config')
}
export default api
@@ -0,0 +1,9 @@
import { Navigate } from 'react-router-dom'
import { getAuthToken } from '../api'
export default function ProtectedRoute({ children }) {
if (!getAuthToken()) {
return <Navigate to="/login" replace />
}
return children
}
@@ -0,0 +1,39 @@
import { createContext, useContext, useState, useEffect } from 'react'
const ThemeContext = createContext()
function getInitialTheme() {
try {
const saved = localStorage.getItem('jyso_theme')
if (saved === 'dark' || saved === 'light') return saved
} catch (e) { /* ignore */ }
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
return 'light'
}
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState(getInitialTheme)
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme)
try { localStorage.setItem('jyso_theme', theme) } catch (e) { /* ignore */ }
}, [theme])
function toggleTheme() {
setTheme(prev => prev === 'light' ? 'dark' : 'light')
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const ctx = useContext(ThemeContext)
if (!ctx) throw new Error('useTheme must be inside ThemeProvider')
return ctx
}
+479
View File
@@ -0,0 +1,479 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--spacing-xs: 6px;
--spacing-sm: 10px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 36px;
--spacing-2xl: 48px;
--bg-primary: #F2F2F7;
--bg-input: rgba(242, 242, 247, 0.64);
--text-primary: #1D1D1F;
--text-secondary: #6E6E73;
--text-muted: #AEAEB2;
--border-color: rgba(60, 60, 67, 0.10);
--accent: #007AFF;
--accent-hover: #0062CC;
--accent-glow: rgba(0, 122, 255, 0.15);
--success: #34C759;
--success-bg: rgba(52, 199, 89, 0.08);
--danger: #FF3B30;
--danger-bg: rgba(255, 59, 48, 0.06);
--card-gap: 20px;
--card-radius: 24px;
--btn-radius: 13px;
--input-radius: 13px;
--font-mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', monospace;
--transition-fast: 200ms cubic-bezier(0.22, 0.1, 0.22, 1);
--transition-fluid: 420ms cubic-bezier(0.22, 0.1, 0.22, 1);
}
[data-theme="dark"] {
--bg-primary: #000000;
--bg-input: rgba(28, 28, 30, 0.60);
--text-primary: #F5F5F7;
--text-secondary: #98989D;
--text-muted: #636366;
--border-color: rgba(84, 84, 88, 0.24);
--accent: #0A84FF;
--accent-hover: #409CFF;
--accent-glow: rgba(10, 132, 255, 0.20);
--success: #30D158;
--success-bg: rgba(48, 209, 88, 0.10);
--danger: #FF453A;
--danger-bg: rgba(255, 69, 58, 0.08);
}
html {
background: var(--bg-primary);
transition: background var(--transition-fluid);
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background var(--transition-fluid), color var(--transition-fluid);
}
code, pre, .mono {
font-family: var(--font-mono);
}
.dashboard-bg {
position: fixed;
inset: 0;
background: var(--bg-primary);
transition: background var(--transition-fluid);
z-index: -1;
}
.page-shell {
max-width: 752px;
margin: 0 auto;
padding: 64px var(--spacing-lg) 48px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 28px;
}
.header h1 {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.3px;
display: flex;
align-items: center;
gap: 10px;
}
.logo-dot {
width: 10px;
height: 10px;
border-radius: 5px;
background: var(--accent);
display: inline-block;
box-shadow: 0 0 12px var(--accent-glow);
}
.header-right {
display: flex;
align-items: center;
gap: 14px;
}
.theme-toggle {
width: 46px;
height: 28px;
border-radius: 14px;
background: var(--bg-input);
border: 1px solid var(--border-color);
cursor: pointer;
position: relative;
transition: background var(--transition-fast), border-color var(--transition-fast);
flex-shrink: 0;
}
.theme-toggle::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
border-radius: 10px;
background: var(--accent);
transition: transform var(--transition-fast);
box-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
[data-theme="dark"] .theme-toggle::after {
transform: translateX(18px);
}
.theme-toggle:hover {
border-color: var(--accent);
}
.logout-btn {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-color);
padding: 7px 16px;
border-radius: var(--btn-radius);
cursor: pointer;
font-family: 'Inter', sans-serif;
font-size: 13px;
font-weight: 500;
transition: all var(--transition-fast);
}
.logout-btn:hover {
background: var(--bg-input);
color: var(--text-primary);
border-color: var(--accent);
}
.glass-card {
padding: 28px;
}
.glass-card h2 {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 24px;
padding-bottom: 14px;
border-bottom: 1px solid var(--border-color);
letter-spacing: -0.2px;
transition: border-color var(--transition-fluid);
}
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(164px, 1fr));
gap: var(--spacing-sm);
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 14px;
transition: background var(--transition-fluid), border-color var(--transition-fluid);
}
.status-label {
font-size: 11px;
font-weight: 500;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-value {
font-size: 13px;
font-weight: 600;
font-family: var(--font-mono);
}
.status-online { color: var(--success); }
.status-offline { color: var(--danger); }
.login-wrapper {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: var(--bg-primary);
transition: background var(--transition-fluid);
}
.login-inner {
padding: 52px 44px 44px;
}
.login-inner h1 {
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
text-align: center;
margin-bottom: 4px;
letter-spacing: -0.6px;
transition: color var(--transition-fluid);
}
.login-inner .subtitle {
text-align: center;
color: var(--text-secondary);
font-size: 14px;
font-weight: 400;
margin-bottom: 4px;
}
.login-inner .tagline {
text-align: center;
color: var(--text-muted);
font-size: 12px;
font-weight: 400;
margin-bottom: 32px;
}
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px 14px;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--input-radius);
color: var(--text-primary);
font-size: 15px;
font-family: 'Inter', sans-serif;
font-weight: 400;
transition: all var(--transition-fast);
outline: none;
}
.form-group input::placeholder,
.form-group textarea::placeholder {
color: var(--text-muted);
font-weight: 300;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-glow);
}
.btn {
padding: 12px 18px;
border: none;
border-radius: var(--btn-radius);
font-size: 15px;
font-family: 'Inter', sans-serif;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-fast);
letter-spacing: -0.2px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-primary {
background: var(--accent);
color: #FFFFFF;
width: 100%;
}
.btn-primary:hover {
background: var(--accent-hover);
box-shadow: 0 2px 14px var(--accent-glow);
transform: translateY(-1px);
}
.btn-primary:disabled {
background: var(--bg-input);
color: var(--text-muted);
cursor: not-allowed;
box-shadow: none;
transform: none;
}
.btn-secondary {
background: transparent;
color: var(--accent);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--bg-input);
border-color: var(--accent);
}
.btn-secondary.active-tab {
background: var(--accent-glow);
border-color: var(--accent);
color: var(--accent);
}
.error-msg {
background: var(--danger-bg);
color: var(--danger);
padding: 10px 14px;
border-radius: 12px;
font-size: 13px;
font-weight: 500;
margin-bottom: 20px;
border: 1px solid var(--danger);
transition: background var(--transition-fluid), color var(--transition-fluid), border-color var(--transition-fluid);
}
.success-msg {
background: var(--success-bg);
color: var(--success);
padding: 10px 14px;
border-radius: 12px;
font-size: 13px;
font-weight: 500;
margin-bottom: 16px;
border: 1px solid var(--success);
transition: background var(--transition-fluid), color var(--transition-fluid), border-color var(--transition-fluid);
}
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.control-bar {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.control-bar .btn { padding: 8px 16px; font-size: 13px; }
.config-form {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.config-form .form-group { margin-bottom: 0; }
.config-form .btn { grid-column: span 2; margin-top: 14px; }
.gadget-list {
max-height: 280px;
overflow-y: auto;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 14px;
transition: background var(--transition-fluid), border-color var(--transition-fluid);
}
.gadget-item {
padding: 10px 14px;
border-bottom: 1px solid var(--border-color);
font-size: 13px;
font-family: var(--font-mono);
cursor: pointer;
transition: background var(--transition-fast), color var(--transition-fast);
}
.gadget-item:last-child { border-bottom: none; }
.gadget-item:hover { background: var(--bg-primary); }
.gadget-item.selected {
background: var(--accent-glow);
color: var(--accent);
font-weight: 500;
}
.payload-output {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 14px;
padding: 14px;
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-secondary);
margin-top: 14px;
white-space: pre-wrap;
max-height: 160px;
overflow-y: auto;
}
input[type="checkbox"] {
accent-color: var(--accent);
width: 18px;
height: 18px;
margin-right: 8px;
cursor: pointer;
}
label.form-group {
display: flex;
align-items: center;
font-size: 14px;
color: var(--text-primary);
font-weight: 500;
cursor: pointer;
}
.pill-sun, .pill-moon {
font-size: 15px;
line-height: 1;
}
@media (max-width: 768px) {
.page-shell { padding: 48px 16px 32px; }
.grid-2 { grid-template-columns: 1fr; }
.config-form { grid-template-columns: 1fr; }
.config-form .btn { grid-column: span 1; }
.login-inner { padding: 36px 28px 32px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
transition-duration: 0ms !important;
animation-duration: 0ms !important;
}
}
+16
View File
@@ -0,0 +1,16 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { HashRouter } from 'react-router-dom'
import { ThemeProvider } from './context/ThemeContext'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<ThemeProvider>
<HashRouter>
<App />
</HashRouter>
</ThemeProvider>
</React.StrictMode>
)
+329
View File
@@ -0,0 +1,329 @@
import { useState, useEffect, useMemo, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import LiquidGlass from 'liquid-glass-react'
import { useTheme } from '../context/ThemeContext'
import {
getStatus, startServers as apiStartServers, getGadgets,
generatePayload as apiGenerate, updateConfig, setAuthToken
} from '../api'
export default function Dashboard() {
const navigate = useNavigate()
const { theme, toggleTheme } = useTheme()
const containerRef = useRef(null)
const [status, setStatus] = useState({})
const [loading, setLoading] = useState(false)
const [msg, setMsg] = useState({ success: '', error: '' })
const [activeTab, setActiveTab] = useState('servers')
const [gadgets, setGadgets] = useState([])
const [gadgetSearch, setGadgetSearch] = useState('')
const [selectedGadget, setSelectedGadget] = useState('')
const [payloadCmd, setPayloadCmd] = useState('')
const [payloadResult, setPayloadResult] = useState('')
const [serverCfg, setServerCfg] = useState({
ldap: false, ldaps: false, http: false, rmi: false
})
const [configForm, setConfigForm] = useState({
ip: '', ldapPort: 1389, ldapsPort: 1669, httpPort: 3456, rmiPort: 1099, codeBase: ''
})
const filteredGadgets = useMemo(() => {
if (!gadgetSearch) return gadgets
return gadgets.filter(g => g.name.toLowerCase().includes(gadgetSearch.toLowerCase()))
}, [gadgetSearch, gadgets])
async function loadStatus() {
try {
const res = await getStatus()
setStatus(res.data)
setConfigForm(prev => ({
...prev,
ip: res.data.ip || '',
ldapPort: res.data.ldapPort || 1389,
ldapsPort: res.data.ldapsPort || 1669,
httpPort: res.data.httpPort || 3456,
rmiPort: res.data.rmiPort || 1099,
codeBase: res.data.codeBase || ''
}))
} catch (e) {
if (e.response && e.response.status === 401) {
setAuthToken(null)
navigate('/login', { replace: true })
}
}
}
async function loadGadgets() {
try {
const res = await getGadgets()
setGadgets(res.data)
} catch (e) { /* ignore */ }
}
async function handleStartServers() {
setLoading(true)
setMsg({ success: '', error: '' })
try {
const res = await apiStartServers({
ldap: serverCfg.ldap, ldaps: serverCfg.ldaps,
http: serverCfg.http, rmi: serverCfg.rmi,
ip: configForm.ip, ldapPort: configForm.ldapPort,
ldapsPort: configForm.ldapsPort, httpPort: configForm.httpPort,
rmiPort: configForm.rmiPort
})
if (res.data.success) {
setMsg({ success: 'Servers starting: ' + (res.data.started.join(', ') || 'none'), error: '' })
}
setTimeout(loadStatus, 2000)
} catch (e) {
setMsg({ success: '', error: 'Failed to start servers' })
} finally { setLoading(false) }
}
async function handleSaveConfig() {
setLoading(true)
setMsg({ success: '', error: '' })
try {
await updateConfig(configForm)
setMsg({ success: 'Configuration saved', error: '' })
loadStatus()
} catch (e) {
setMsg({ success: '', error: 'Failed to save configuration' })
} finally { setLoading(false) }
}
async function handleGeneratePayload() {
setLoading(true)
setMsg({ success: '', error: '' })
try {
const res = await apiGenerate({ gadget: selectedGadget, command: payloadCmd })
if (res.data.success) {
setPayloadResult(res.data.message || 'Payload generated successfully')
setMsg({ success: 'Payload generated', error: '' })
} else {
setPayloadResult('Error: ' + (res.data.error || 'unknown'))
setMsg({ success: '', error: res.data.error || 'Generation failed' })
}
} catch (e) {
setMsg({ success: '', error: 'Failed to generate payload' })
} finally { setLoading(false) }
}
function logout() {
setAuthToken(null)
navigate('/login', { replace: true })
}
useEffect(() => { loadStatus(); loadGadgets() }, [])
const liquidProps = {
mouseContainer: containerRef,
elasticity: 0.07,
blurAmount: 0.042,
saturation: 120,
displacementScale: 36,
cornerRadius: 24,
overLight: theme === 'light',
}
return (
<div ref={containerRef}>
<div className="dashboard-bg" />
<div className="page-shell">
<LiquidGlass {...liquidProps} style={{ marginBottom: 20 }}>
<div className="header">
<h1>
<span className="logo-dot" />
JYso
</h1>
<div className="header-right">
<button
className="theme-toggle"
onClick={toggleTheme}
aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
title={theme === 'light' ? 'Dark mode' : 'Light mode'}
>
<span className="pill-sun" style={{ position: 'absolute', top: 4, left: 5, opacity: theme === 'dark' ? 0.35 : 1, transition: 'opacity 0.2s' }}>
&#x2600;&#xFE0F;
</span>
<span className="pill-moon" style={{ position: 'absolute', top: 4, right: 5, opacity: theme === 'dark' ? 1 : 0.35, transition: 'opacity 0.2s' }}>
&#x1F319;
</span>
</button>
<button className="logout-btn" onClick={logout}>Logout</button>
</div>
</div>
</LiquidGlass>
{msg.success && <div className="success-msg">{msg.success}</div>}
{msg.error && <div className="error-msg">{msg.error}</div>}
<LiquidGlass {...liquidProps} style={{ marginBottom: 20 }}>
<div className="glass-card">
<h2>Server Status</h2>
<div className="status-grid">
<div className="status-item">
<span className="status-label">LDAP ({status.ldapPort})</span>
<span className={'status-value ' + (status.ldapRunning ? 'status-online' : 'status-offline')}>
{status.ldapRunning ? 'ONLINE' : 'OFFLINE'}
</span>
</div>
<div className="status-item">
<span className="status-label">LDAPS ({status.ldapsPort})</span>
<span className={'status-value ' + (status.ldapsRunning ? 'status-online' : 'status-offline')}>
{status.ldapsRunning ? 'ONLINE' : 'OFFLINE'}
</span>
</div>
<div className="status-item">
<span className="status-label">HTTP ({status.httpPort})</span>
<span className={'status-value ' + (status.httpRunning ? 'status-online' : 'status-offline')}>
{status.httpRunning ? 'ONLINE' : 'OFFLINE'}
</span>
</div>
<div className="status-item">
<span className="status-label">RMI ({status.rmiPort})</span>
<span className={'status-value ' + (status.rmiRunning ? 'status-online' : 'status-offline')}>
{status.rmiRunning ? 'ONLINE' : 'OFFLINE'}
</span>
</div>
<div className="status-item">
<span className="status-label">IP Address</span>
<span className="status-value" style={{ color: 'var(--accent)' }}>{status.ip || '0.0.0.0'}</span>
</div>
<div className="status-item">
<span className="status-label">Version</span>
<span className="status-value" style={{ color: 'var(--accent)' }}>{status.version || '-'}</span>
</div>
</div>
</div>
</LiquidGlass>
<LiquidGlass {...liquidProps}>
<div className="glass-card">
<h2>Controls</h2>
<div className="control-bar">
<button className="btn btn-primary" style={{ width: 'auto' }} onClick={loadStatus}>
Refresh Status
</button>
<button
className={'btn btn-secondary' + (activeTab === 'servers' ? ' active-tab' : '')}
onClick={() => setActiveTab('servers')}>Servers</button>
<button
className={'btn btn-secondary' + (activeTab === 'config' ? ' active-tab' : '')}
onClick={() => setActiveTab('config')}>Configuration</button>
<button
className={'btn btn-secondary' + (activeTab === 'payload' ? ' active-tab' : '')}
onClick={() => setActiveTab('payload')}>Payload Generator</button>
</div>
{activeTab === 'servers' && (
<div>
<div className="grid-2">
<label className="form-group">
<input type="checkbox" checked={serverCfg.ldap}
onChange={e => setServerCfg({ ...serverCfg, ldap: e.target.checked })} /> LDAP
</label>
<label className="form-group">
<input type="checkbox" checked={serverCfg.ldaps}
onChange={e => setServerCfg({ ...serverCfg, ldaps: e.target.checked })} /> LDAPS
</label>
<label className="form-group">
<input type="checkbox" checked={serverCfg.http}
onChange={e => setServerCfg({ ...serverCfg, http: e.target.checked })} /> HTTP
</label>
<label className="form-group">
<input type="checkbox" checked={serverCfg.rmi}
onChange={e => setServerCfg({ ...serverCfg, rmi: e.target.checked })} /> RMI
</label>
</div>
<button className="btn btn-primary" style={{ marginTop: 16 }} onClick={handleStartServers} disabled={loading}>
{loading ? 'Starting...' : 'Start Selected Servers'}
</button>
</div>
)}
{activeTab === 'config' && (
<div>
<div className="config-form">
<div className="form-group">
<label>IP Address</label>
<input type="text" value={configForm.ip}
onChange={e => setConfigForm({ ...configForm, ip: e.target.value })} />
</div>
<div className="form-group">
<label>LDAP Port</label>
<input type="number" value={configForm.ldapPort}
onChange={e => setConfigForm({ ...configForm, ldapPort: parseInt(e.target.value) || 1389 })} />
</div>
<div className="form-group">
<label>LDAPS Port</label>
<input type="number" value={configForm.ldapsPort}
onChange={e => setConfigForm({ ...configForm, ldapsPort: parseInt(e.target.value) || 1669 })} />
</div>
<div className="form-group">
<label>HTTP Port</label>
<input type="number" value={configForm.httpPort}
onChange={e => setConfigForm({ ...configForm, httpPort: parseInt(e.target.value) || 3456 })} />
</div>
<div className="form-group">
<label>RMI Port</label>
<input type="number" value={configForm.rmiPort}
onChange={e => setConfigForm({ ...configForm, rmiPort: parseInt(e.target.value) || 1099 })} />
</div>
<div className="form-group">
<label>CodeBase URL</label>
<input type="text" value={configForm.codeBase} placeholder="http://x.x.x.x:port/"
onChange={e => setConfigForm({ ...configForm, codeBase: e.target.value })} />
</div>
<button className="btn btn-primary" onClick={handleSaveConfig} disabled={loading}>
{loading ? 'Saving...' : 'Save Configuration'}
</button>
</div>
</div>
)}
{activeTab === 'payload' && (
<div className="grid-2">
<div>
<h3 style={{ fontSize: 13, marginBottom: 10, color: 'var(--text-secondary)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>
Select Gadget
</h3>
<div className="form-group">
<input type="text" placeholder="Search..." value={gadgetSearch}
onChange={e => setGadgetSearch(e.target.value)} />
</div>
<div className="gadget-list">
{filteredGadgets.map(g => (
<div key={g.name}
className={'gadget-item' + (selectedGadget === g.name ? ' selected' : '')}
onClick={() => setSelectedGadget(g.name)}>
{g.name}
</div>
))}
</div>
</div>
<div>
<div className="form-group">
<label>Command</label>
<textarea rows={4} value={payloadCmd} placeholder="e.g. whoami"
onChange={e => setPayloadCmd(e.target.value)} />
</div>
<button className="btn btn-primary" onClick={handleGeneratePayload}
disabled={loading || !selectedGadget}>
{loading ? 'Generating...' : 'Generate Payload'}
</button>
{payloadResult && (
<div className="payload-output">{payloadResult}</div>
)}
</div>
</div>
)}
</div>
</LiquidGlass>
</div>
</div>
)
}
+102
View File
@@ -0,0 +1,102 @@
import { useState, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import LiquidGlass from 'liquid-glass-react'
import { login, setAuthToken } from '../api'
import { useTheme } from '../context/ThemeContext'
export default function Login() {
const navigate = useNavigate()
const { theme, toggleTheme } = useTheme()
const containerRef = useRef(null)
const [username, setUsername] = useState('qi')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
async function handleSubmit(e) {
e.preventDefault()
setError('')
setLoading(true)
try {
const res = await login(username, password)
if (res.data && res.data.token) {
setAuthToken(res.data.token)
navigate('/dashboard', { replace: true })
} else {
setError('Unexpected response from server')
}
} catch (e) {
if (e.response) {
if (e.response.status === 401) {
setError('Invalid username or password.')
} else {
setError('Server error: ' + e.response.status)
}
} else if (e.request) {
setError('Cannot connect to server. Is it running?')
} else {
setError('Error: ' + e.message)
}
setPassword('')
} finally {
setLoading(false)
}
}
return (
<div className="login-wrapper" ref={containerRef}>
<div style={{ position: 'fixed', top: 22, right: 22, zIndex: 200 }}>
<button
className="theme-toggle"
onClick={toggleTheme}
aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
title={theme === 'light' ? 'Dark mode' : 'Light mode'}
/>
</div>
<LiquidGlass
mouseContainer={containerRef}
elasticity={0.10}
blurAmount={0.045}
saturation={128}
displacementScale={44}
cornerRadius={28}
aberrationIntensity={1.8}
overLight={theme === 'light'}
style={{ width: 380 }}
>
<div className="login-inner">
<h1>
<span className="logo-dot" style={{ width: 12, height: 12, borderRadius: 6 }} />
JYso
</h1>
<p className="subtitle">JNDI Exploitation Toolkit</p>
<p className="tagline">Sign in to access the dashboard</p>
{error && <div className="error-msg">{error}</div>}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Username</label>
<input
type="text"
value={username}
onChange={e => setUsername(e.target.value)}
placeholder="Enter username"
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="Enter password"
/>
</div>
<button className="btn btn-primary" type="submit" disabled={loading}>
{loading ? 'Logging in...' : 'Sign In'}
</button>
</form>
</div>
</LiquidGlass>
</div>
)
}
+19
View File
@@ -0,0 +1,19 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
outDir: '../resources/static',
emptyOutDir: true,
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
}
}
}
})
@@ -0,0 +1,117 @@
package com.qi4l.JYso.web;
import com.qi4l.JYso.HTTPServer;
import com.qi4l.JYso.LdapServer;
import com.qi4l.JYso.LdapsServer;
import com.qi4l.JYso.RMIServer;
import com.qi4l.JYso.gadgets.Config.Config;
import com.qi4l.JYso.web.config.JYsoWebPasswordProvider;
import com.qi4l.JYso.web.config.WebPasswordGenerator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import static com.qi4l.JYso.gadgets.Config.Config.logo;
import static org.fusesource.jansi.Ansi.ansi;
@SpringBootApplication(scanBasePackages = "com.qi4l.JYso.web")
public class JYsoWebApplication {
private static final Logger log = LogManager.getLogger(JYsoWebApplication.class);
public static String webPassword;
public static boolean jndiServersStarted = false;
public static void start(String[] args) {
logo();
webPassword = WebPasswordGenerator.generatePassword();
JYsoWebPasswordProvider.setPassword(webPassword);
Config.applyCmdArgs(filterWebArgs(args));
int webPort = 8080;
for (int i = 0; i < args.length; i++) {
if ("-wP".equals(args[i]) || "--webPort".equals(args[i])) {
if (i + 1 < args.length) {
webPort = Integer.parseInt(args[i + 1]);
}
}
}
System.out.println(ansi().render("@|green [+]|@ Web GUI starting >> http://127.0.0.1:" + webPort));
System.out.println(ansi().render("@|yellow [+]|@ ============================================"));
System.out.println(ansi().render("@|yellow [+]|@ Username: qi"));
System.out.println(ansi().render("@|yellow [+]|@ Password: " + webPassword));
System.out.println(ansi().render("@|yellow [+]|@ ============================================"));
startJndiServers(args);
Properties props = new Properties();
props.put("server.port", String.valueOf(webPort));
props.put("spring.main.banner-mode", "off");
props.put("spring.main.web-application-type", "servlet");
ConfigurableApplicationContext ctx = new SpringApplicationBuilder(JYsoWebApplication.class)
.web(WebApplicationType.SERVLET)
.properties(props)
.run(filterWebArgs(args));
CountDownLatch latch = new CountDownLatch(1);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("Shutting down...");
ctx.close();
latch.countDown();
}));
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private static String[] filterWebArgs(String[] args) {
java.util.List<String> filtered = new java.util.ArrayList<>();
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if ("-w".equals(arg) || "--web".equals(arg)) {
} else if ("-wP".equals(arg) || "--webPort".equals(arg)) {
i++;
} else if ("--with-jndi".equals(arg)) {
} else {
filtered.add(arg);
}
}
return filtered.toArray(new String[0]);
}
private static void startJndiServers(String[] args) {
for (String arg : args) {
if ("--with-jndi".equals(arg)) {
jndiServersStarted = true;
new Thread(() -> {
try { LdapServer.start(); } catch (Exception e) { log.error("LDAP start failed", e); }
}, "ldap-server").start();
new Thread(() -> {
try { HTTPServer.start(); } catch (Exception e) { log.error("HTTP start failed", e); }
}, "http-server").start();
new Thread(() -> {
try { RMIServer.start(); } catch (Exception e) { log.error("RMI start failed", e); }
}, "rmi-server").start();
if (Config.TLSProxy) {
new Thread(() -> {
try { LdapsServer.start(); } catch (Exception e) { log.error("LDAPS start failed", e); }
}, "ldaps-server").start();
}
break;
}
}
}
}
@@ -0,0 +1,14 @@
package com.qi4l.JYso.web.config;
public class JYsoWebPasswordProvider {
private static String password;
public static void setPassword(String pwd) {
password = pwd;
}
public static String getPassword() {
return password;
}
}
@@ -0,0 +1,51 @@
package com.qi4l.JYso.web.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.builder()
.username("qi")
.password(passwordEncoder().encode(JYsoWebPasswordProvider.getPassword()))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.addFilterBefore(new TokenAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@@ -0,0 +1,43 @@
package com.qi4l.JYso.web.config;
import com.qi4l.JYso.web.controller.AuthController;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TokenAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String path = request.getRequestURI();
if (!path.startsWith("/api/") || path.equals("/api/auth/login")) {
filterChain.doFilter(request, response);
return;
}
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Unauthorized\"}");
return;
}
String token = authHeader.substring(7);
if (!AuthController.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Invalid token\"}");
return;
}
filterChain.doFilter(request, response);
}
}
@@ -0,0 +1,31 @@
package com.qi4l.JYso.web.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
registry.addViewController("/{path:[^\\.]*}").setViewName("forward:/index.html");
}
}
@@ -0,0 +1,17 @@
package com.qi4l.JYso.web.config;
import java.security.SecureRandom;
public class WebPasswordGenerator {
private static final String CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%^&*";
private static final SecureRandom RANDOM = new SecureRandom();
public static String generatePassword() {
StringBuilder sb = new StringBuilder(13);
for (int i = 0; i < 13; i++) {
sb.append(CHARS.charAt(RANDOM.nextInt(CHARS.length())));
}
return sb.toString();
}
}
@@ -0,0 +1,149 @@
package com.qi4l.JYso.web.controller;
import com.qi4l.JYso.HTTPServer;
import com.qi4l.JYso.LdapServer;
import com.qi4l.JYso.LdapsServer;
import com.qi4l.JYso.RMIServer;
import com.qi4l.JYso.Starter;
import com.qi4l.JYso.gadgets.Config.Config;
import com.qi4l.JYso.gadgets.Config.ysoserial;
import com.qi4l.JYso.gadgets.ObjectPayload;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/status")
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("ldapRunning", LdapServer.isRunning);
status.put("ldapsRunning", LdapsServer.isRunning);
status.put("httpRunning", HTTPServer.isRunning);
status.put("rmiRunning", RMIServer.isRunning);
status.put("ip", Config.ip);
status.put("ldapPort", Config.ldapPort);
status.put("ldapsPort", Config.ldapsPort);
status.put("httpPort", Config.httpPort);
status.put("rmiPort", Config.rmiPort);
status.put("codeBase", Config.codeBase);
status.put("version", "1.3.7");
return status;
}
@PostMapping("/servers/start")
public Map<String, Object> startServers(@RequestBody Map<String, Object> body) {
Map<String, Object> result = new LinkedHashMap<>();
List<String> started = new ArrayList<>();
List<String> errors = new ArrayList<>();
boolean startLdap = body.get("ldap") != null && Boolean.TRUE.equals(body.get("ldap"));
boolean startLdaps = body.get("ldaps") != null && Boolean.TRUE.equals(body.get("ldaps"));
boolean startHttp = body.get("http") != null && Boolean.TRUE.equals(body.get("http"));
boolean startRmi = body.get("rmi") != null && Boolean.TRUE.equals(body.get("rmi"));
if (body.containsKey("ip")) Config.ip = (String) body.get("ip");
if (body.containsKey("ldapPort")) Config.ldapPort = (Integer) body.get("ldapPort");
if (body.containsKey("ldapsPort")) Config.ldapsPort = (Integer) body.get("ldapsPort");
if (body.containsKey("httpPort")) Config.httpPort = (Integer) body.get("httpPort");
if (body.containsKey("rmiPort")) Config.rmiPort = (Integer) body.get("rmiPort");
if (startLdap && !LdapServer.isRunning) {
new Thread(() -> {
try { LdapServer.start(); } catch (Exception ignored) {}
}, "ldap-starter").start();
started.add("LDAP");
}
if (startHttp && !HTTPServer.isRunning) {
new Thread(() -> {
try { HTTPServer.start(); } catch (Exception ignored) {}
}, "http-starter").start();
started.add("HTTP");
}
if (startLdaps && !LdapsServer.isRunning) {
new Thread(() -> {
try { LdapsServer.start(); } catch (Exception ignored) {}
}, "ldaps-starter").start();
started.add("LDAPS");
}
if (startRmi && !RMIServer.isRunning) {
new Thread(() -> {
try { RMIServer.start(); } catch (Exception ignored) {}
}, "rmi-starter").start();
started.add("RMI");
}
result.put("started", started);
result.put("errors", errors);
result.put("success", true);
return result;
}
@GetMapping("/gadgets")
public List<Map<String, Object>> listGadgets() {
List<Map<String, Object>> list = new ArrayList<>();
for (String name : Starter.caseInsensitiveObjectPayloadMap.keySet()) {
Map<String, Object> item = new LinkedHashMap<>();
String shortName = name.substring(name.lastIndexOf('.') + 1);
item.put("name", shortName);
item.put("fullName", name);
list.add(item);
}
return list;
}
@PostMapping("/payload/generate")
public Map<String, Object> generatePayload(@RequestBody Map<String, Object> body) {
Map<String, Object> result = new LinkedHashMap<>();
String gadget = (String) body.get("gadget");
String command = (String) body.get("command");
if (gadget == null || command == null) {
result.put("success", false);
result.put("error", "gadget and command are required");
return result;
}
try {
Starter.JYsoMode = true;
String[] args = new String[]{
"-y",
"-g", gadget,
"-p", command
};
ysoserial.run(args);
result.put("success", true);
result.put("message", "Payload generated successfully");
} catch (Exception e) {
result.put("success", false);
result.put("error", e.getMessage());
}
return result;
}
@PostMapping("/config/update")
public Map<String, Object> updateConfig(@RequestBody Map<String, Object> body) {
Map<String, Object> result = new LinkedHashMap<>();
if (body.containsKey("ip")) Config.ip = (String) body.get("ip");
if (body.containsKey("ldapPort")) Config.ldapPort = (Integer) body.get("ldapPort");
if (body.containsKey("ldapsPort")) Config.ldapsPort = (Integer) body.get("ldapsPort");
if (body.containsKey("httpPort")) Config.httpPort = (Integer) body.get("httpPort");
if (body.containsKey("rmiPort")) Config.rmiPort = (Integer) body.get("rmiPort");
if (body.containsKey("command")) Config.command = (String) body.get("command");
if (body.containsKey("codeBase")) Config.codeBase = (String) body.get("codeBase");
result.put("success", true);
result.put("config", getStatus());
return result;
}
@GetMapping("/config")
public Map<String, Object> getConfig() {
return getStatus();
}
}
@@ -0,0 +1,40 @@
package com.qi4l.JYso.web.controller;
import com.qi4l.JYso.web.config.JYsoWebPasswordProvider;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private static final ConcurrentHashMap<String, String> tokens = new ConcurrentHashMap<>();
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody Map<String, String> body) {
String username = body.get("username");
String password = body.get("password");
if (!"qi".equals(username) || !JYsoWebPasswordProvider.getPassword().equals(password)) {
Map<String, String> error = new HashMap<>();
error.put("error", "Invalid credentials");
return ResponseEntity.status(401).body(error);
}
String token = UUID.randomUUID().toString();
tokens.put(token, username);
Map<String, String> result = new HashMap<>();
result.put("token", token);
result.put("username", username);
return ResponseEntity.ok(result);
}
public static boolean validateToken(String token) {
return token != null && tokens.containsKey(token);
}
}
+11
View File
@@ -0,0 +1,11 @@
spring.application.name=JYso
server.port=8080
spring.main.web-application-type=servlet
spring.main.banner-mode=off
logging.level.root=ERROR
logging.level.org.springframework=ERROR
logging.level.org.apache=ERROR
logging.level.org.hibernate=ERROR
logging.level.org.eclipse=ERROR
logging.level.org.jboss=ERROR
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>