init
This commit is contained in:
26
front/.gitignore
vendored
Normal file
26
front/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.router
|
||||
377
front/bun.lock
Normal file
377
front/bun.lock
Normal file
@@ -0,0 +1,377 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "svelte-app",
|
||||
"dependencies": {
|
||||
"@lucide/svelte": "^0.542.0",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"axios": "^1.12.1",
|
||||
"clsx": "^2.1.1",
|
||||
"marked": "^16.3.0",
|
||||
"path": "^0.12.7",
|
||||
"sv-router": "latest",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"theme-change": "^2.5.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/typography": "^0.5.18",
|
||||
"daisyui": "^5.1.6",
|
||||
"svelte": "^5.20.2",
|
||||
"svelte-check": "^4.1.4",
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||
|
||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
|
||||
|
||||
"@lucide/svelte": ["@lucide/svelte@0.542.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-NuWttxTVfMSURpOxcKiKvoCtma3JtEpcJWzF/0cO69saZfXlv6G8NYAvEEGLmk75YPl+I+ROe+F97WhddM8r2A=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.0", "", { "os": "android", "cpu": "arm" }, "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.0", "", { "os": "android", "cpu": "arm64" }, "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.50.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.50.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.50.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.50.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.50.0", "", { "os": "linux", "cpu": "arm" }, "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.50.0", "", { "os": "linux", "cpu": "arm" }, "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.50.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.50.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ=="],
|
||||
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.50.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.50.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.50.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.50.0", "", { "os": "linux", "cpu": "x64" }, "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.50.0", "", { "os": "none", "cpu": "arm64" }, "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.50.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.50.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg=="],
|
||||
|
||||
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
|
||||
|
||||
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.1.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ=="],
|
||||
|
||||
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.12", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.12" } }, "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.12", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.12", "@tailwindcss/oxide-darwin-arm64": "4.1.12", "@tailwindcss/oxide-darwin-x64": "4.1.12", "@tailwindcss/oxide-freebsd-x64": "4.1.12", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", "@tailwindcss/oxide-linux-x64-musl": "4.1.12", "@tailwindcss/oxide-wasm32-wasi": "4.1.12", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" } }, "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.12", "", { "os": "android", "cpu": "arm64" }, "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12", "", { "os": "linux", "cpu": "arm" }, "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.12", "", { "os": "linux", "cpu": "x64" }, "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.12", "", { "os": "linux", "cpu": "x64" }, "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.12", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@emnapi/wasi-threads": "^1.0.4", "@napi-rs/wasm-runtime": "^0.2.12", "@tybys/wasm-util": "^0.10.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.12", "", { "os": "win32", "cpu": "x64" }, "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA=="],
|
||||
|
||||
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.18", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-dDIgwZOlf+tVkZ7A029VvQ1+ngKATENDjMEx2N35s2yPjfTS05RWSM8ilhEWSa5DMJ6ci2Ha9WNZEd2GQjrdQg=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.12", "", { "dependencies": { "@tailwindcss/node": "4.1.12", "@tailwindcss/oxide": "4.1.12", "tailwindcss": "4.1.12" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"axios": ["axios@1.12.1", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
|
||||
"daisyui": ["daisyui@5.1.6", "", {}, "sha512-KCzv25f+3lwWbfnPZZG9Xo0kSGO1NSysyIiS5AoCtDotIrvvArggHklCey1Fg6U2gZuqxsi2rptT1q3khoYCMw=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "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": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
|
||||
|
||||
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||
|
||||
"esrap": ["esrap@2.1.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
||||
|
||||
"form-data": ["form-data@4.0.4", "", { "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-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "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": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"inherits": ["inherits@2.0.3", "", {}, "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="],
|
||||
|
||||
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
||||
|
||||
"jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
|
||||
|
||||
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="],
|
||||
|
||||
"marked": ["marked@16.3.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="],
|
||||
|
||||
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
|
||||
|
||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"path": ["path@0.12.7", "", { "dependencies": { "process": "^0.11.1", "util": "^0.10.3" } }, "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
|
||||
|
||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"rollup": ["rollup@4.50.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.0", "@rollup/rollup-android-arm64": "4.50.0", "@rollup/rollup-darwin-arm64": "4.50.0", "@rollup/rollup-darwin-x64": "4.50.0", "@rollup/rollup-freebsd-arm64": "4.50.0", "@rollup/rollup-freebsd-x64": "4.50.0", "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", "@rollup/rollup-linux-arm-musleabihf": "4.50.0", "@rollup/rollup-linux-arm64-gnu": "4.50.0", "@rollup/rollup-linux-arm64-musl": "4.50.0", "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", "@rollup/rollup-linux-ppc64-gnu": "4.50.0", "@rollup/rollup-linux-riscv64-gnu": "4.50.0", "@rollup/rollup-linux-riscv64-musl": "4.50.0", "@rollup/rollup-linux-s390x-gnu": "4.50.0", "@rollup/rollup-linux-x64-gnu": "4.50.0", "@rollup/rollup-linux-x64-musl": "4.50.0", "@rollup/rollup-openharmony-arm64": "4.50.0", "@rollup/rollup-win32-arm64-msvc": "4.50.0", "@rollup/rollup-win32-ia32-msvc": "4.50.0", "@rollup/rollup-win32-x64-msvc": "4.50.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw=="],
|
||||
|
||||
"runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
|
||||
|
||||
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"sv-router": ["sv-router@0.8.1", "", { "dependencies": { "esm-env": "^1.2.2" }, "peerDependencies": { "svelte": "^5" }, "bin": { "sv-router": "src/cli/index.js" } }, "sha512-evUoE6TFEB89u8FzgEXiqj8aCTTCbCrWLBbfPe8qZxbNUT5I0sARoTLIZJtBNuumXe+FxpCCDrb8v/d6va+ZVQ=="],
|
||||
|
||||
"svelte": ["svelte@5.38.6", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ltBPlkvqk3bgCK7/N323atUpP3O3Y+DrGV4dcULrsSn4fZaaNnOmdplNznwfdWclAgvSr5rxjtzn/zJhRm6TKg=="],
|
||||
|
||||
"svelte-check": ["svelte-check@4.3.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg=="],
|
||||
|
||||
"svelte-sonner": ["svelte-sonner@1.0.5", "", { "dependencies": { "runed": "^0.28.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-9dpGPFqKb/QWudYqGnEz93vuY+NgCEvyNvxoCLMVGw6sDN/3oVeKV1xiEirW2E1N3vJEyj5imSBNOGltQHA7mg=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.12", "", {}, "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA=="],
|
||||
|
||||
"tapable": ["tapable@2.2.3", "", {}, "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg=="],
|
||||
|
||||
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
|
||||
|
||||
"theme-change": ["theme-change@2.5.0", "", {}, "sha512-B/UdsgdHAGhSKHTAQnxg/etN0RaMDpehuJmZIjLMDVJ6DGIliRHGD6pODi1CXLQAN9GV0GSyB3G6yCuK05PkPQ=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"util": ["util@0.10.4", "", { "dependencies": { "inherits": "2.0.3" } }, "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
}
|
||||
}
|
||||
14
front/index.html
Normal file
14
front/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Eleakxir</title>
|
||||
<meta name="description" content="Eleakxir is a self-hosted search engine that lets you connect to your own private and secure server, explore data wells (parquet files) from multiple sources, and visualize results in a clean, modern web interface.">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" style="display: contents"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
34
front/package.json
Normal file
34
front/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "svelte-app",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check",
|
||||
"postinstall": "sv-router"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lucide/svelte": "^0.542.0",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"axios": "^1.12.1",
|
||||
"clsx": "^2.1.1",
|
||||
"marked": "^16.3.0",
|
||||
"path": "^0.12.7",
|
||||
"sv-router": "latest",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"theme-change": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/typography": "^0.5.18",
|
||||
"daisyui": "^5.1.6",
|
||||
"svelte": "^5.20.2",
|
||||
"svelte-check": "^4.1.4",
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.2.0"
|
||||
}
|
||||
}
|
||||
3
front/public/favicon.svg
Normal file
3
front/public/favicon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="141" height="205" viewBox="0 0 141 205" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M69.7444 0C84.49 25.1637 100.559 49.4708 117.95 72.9219C126.451 85.0149 133.113 98.1035 137.934 112.188C144.168 135.735 140.195 157.355 126.014 177.046C109.938 196.591 89.1945 205.53 63.7844 203.865C36.717 200.867 17.4935 187.019 6.11353 162.321C-1.9191 142.522 -2.03583 122.655 5.76294 102.722C9.71019 93.7604 14.3849 85.2295 19.7864 77.1289C35.0593 56.0531 49.5504 34.4338 63.259 12.2705C65.6086 8.27249 67.7699 4.18207 69.7444 0ZM100.596 81.3359C102.957 92.649 102.198 103.751 98.3176 114.642C93.9276 124.99 87.7338 134.105 79.7366 141.987C77.6951 144.434 75.8254 147.005 74.1272 149.7C70.5033 155.43 68.5745 161.682 68.342 168.456C68.1692 175.079 70.6236 180.455 75.7043 184.583C89.1062 183.345 100.267 177.678 109.186 167.58C123.101 149.518 125.672 129.885 116.899 108.682C112.25 99.0223 106.815 89.9071 100.596 81.3359ZM70.095 36.4609C59.5617 53.4794 48.4018 70.0738 36.6145 86.2441C30.2619 94.8335 25.2366 104.183 21.5393 114.291C15.2642 135.159 19.2377 153.74 33.4592 170.034C39.7381 176.533 47.2756 180.798 56.0715 182.83C55.7506 182.699 55.4583 182.524 55.1956 182.305C40.6021 163.32 39.6671 143.687 52.3909 123.406C58.721 114.622 64.9148 105.74 70.9719 96.7617C72.559 94.0549 73.9614 91.2501 75.179 88.3477C81.8825 70.1929 80.1876 52.8971 70.095 36.4609Z" fill="#DDD6DF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
front/public/l.png
Normal file
BIN
front/public/l.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 426 KiB |
6
front/src/App.svelte
Normal file
6
front/src/App.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
import './app.css'
|
||||
import { Router } from 'sv-router';
|
||||
</script>
|
||||
|
||||
<Router />
|
||||
94
front/src/app.css
Normal file
94
front/src/app.css
Normal file
@@ -0,0 +1,94 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@plugin "daisyui" {
|
||||
themes:
|
||||
light,
|
||||
dark --prefersdark --default;
|
||||
}
|
||||
|
||||
@plugin "daisyui/theme" {
|
||||
name: "dark";
|
||||
default: true; /* set as default */
|
||||
prefersdark: true; /* set as default dark mode (prefers-color-scheme:dark) */
|
||||
color-scheme: dark; /* color of browser-provided UI */
|
||||
|
||||
--color-base-100: oklch(0.2096 0.0275 290.36);
|
||||
--color-base-200: oklch(0.1896 0.0242 287.67);
|
||||
--color-base-300: oklch(0.1674 0.0229 292.08);
|
||||
--color-base-content: oklch(0.841 0.0056 297.71);
|
||||
|
||||
--color-primary: oklch(0.5454 0.2756 292.04);
|
||||
--color-primary-content: oklch(0.9074 0.049167 293.0386);
|
||||
|
||||
--color-secondary: oklch(0.5103 0.2756 292.04);
|
||||
--color-secondary-content: oklch(0.9074 0.049167 293.0386);
|
||||
|
||||
--color-accent: oklch(0.6241 0.1575 277.95);
|
||||
--color-accent-content: oklch(0.1248 0.031 280.93);
|
||||
|
||||
--color-neutral: oklch(0.2813 0.0153 269.13);
|
||||
--color-neutral-content: oklch(0.8574 0.003 264.54);
|
||||
|
||||
--radius-selector: 1rem;
|
||||
--radius-field: 0.5rem;
|
||||
--radius-box: 1rem;
|
||||
--size-selector: 0.25rem;
|
||||
--size-field: 0.25rem;
|
||||
--border: 1px;
|
||||
--depth: 0;
|
||||
--noise: 0;
|
||||
}
|
||||
|
||||
@plugin "daisyui/theme" {
|
||||
name: "light";
|
||||
default: false; /* set as default */
|
||||
prefersdark: false; /* set as default dark mode (prefers-color-scheme:dark) */
|
||||
color-scheme: light; /* color of browser-provided UI */
|
||||
|
||||
--color-primary: oklch(0.5454 0.2756 292.04);
|
||||
--color-primary-content: oklch(0.9074 0.049167 293.0386);
|
||||
|
||||
--color-secondary: oklch(0.5103 0.2756 292.04);
|
||||
--color-secondary-content: oklch(0.9074 0.049167 293.0386);
|
||||
|
||||
--color-accent: oklch(0.6241 0.1575 277.95);
|
||||
--color-accent-content: oklch(0.1248 0.031 280.93);
|
||||
|
||||
--radius-selector: 1rem;
|
||||
--radius-field: 0.5rem;
|
||||
--radius-box: 1rem;
|
||||
--size-selector: 0.25rem;
|
||||
--size-field: 0.25rem;
|
||||
--border: 1px;
|
||||
--depth: 0;
|
||||
--noise: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
@apply mx-auto w-full px-6;
|
||||
}
|
||||
|
||||
.h1 {
|
||||
@apply scroll-m-20 text-4xl sm:text-5xl font-extrabold tracking-tight lg:text-5xl;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply scroll-m-20 text-3xl sm:text-4xl font-semibold tracking-tight transition-colors;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
@apply scroll-m-20 text-2xl font-semibold tracking-tight transition-colors;
|
||||
}
|
||||
|
||||
.h4 {
|
||||
@apply scroll-m-20 text-xl font-semibold tracking-tight transition-colors;
|
||||
}
|
||||
|
||||
.h5 {
|
||||
@apply scroll-m-20 text-lg font-semibold tracking-tight transition-colors;
|
||||
}
|
||||
|
||||
.h6 {
|
||||
@apply scroll-m-20 text-base font-semibold tracking-tight transition-colors;
|
||||
}
|
||||
79
front/src/lib/components/accordion.svelte
Normal file
79
front/src/lib/components/accordion.svelte
Normal file
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
type Icon as IconType,
|
||||
} from "@lucide/svelte";
|
||||
import type { Snippet } from "svelte";
|
||||
import { cn } from "../utils";
|
||||
|
||||
let isOpen = $state<boolean>(false);
|
||||
|
||||
const {
|
||||
imageUrl,
|
||||
icon,
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
}: {
|
||||
imageUrl?: string | null;
|
||||
icon: typeof IconType;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={cn("list-row text-left bg-base-200/40",
|
||||
children != null ? "cursor-pointer hover:bg-base-300/75" : ""
|
||||
)}
|
||||
class:bg-base-300={isOpen}
|
||||
class:rounded-b-none={isOpen}
|
||||
onclick={() => {
|
||||
if (children != null) {
|
||||
isOpen = !isOpen;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{#if imageUrl && imageUrl.length > 0}
|
||||
<img
|
||||
src="https://icons.duckduckgo.com/ip3/{imageUrl}.ico"
|
||||
class="size-10 rounded-box bg-neutral"
|
||||
alt="Favicon of {imageUrl}"
|
||||
/>
|
||||
{:else}
|
||||
{@const Icon = icon}
|
||||
<div
|
||||
class="size-10 rounded-box bg-neutral items-center justify-center flex"
|
||||
>
|
||||
<Icon />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<div class="font-semibold">{title}</div>
|
||||
{#if subtitle != null && subtitle.length !== 0}
|
||||
<div class="text-xs uppercase font-semibold opacity-60">
|
||||
{subtitle}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if children != null}
|
||||
<div class="btn btn-square btn-ghost">
|
||||
{#if isOpen}
|
||||
<ChevronUp size={12} />
|
||||
{:else}
|
||||
<ChevronDown size={12} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{#if children != null}
|
||||
{#if isOpen}
|
||||
<li class="list-row bg-base-200 rounded-t-none mb-2">
|
||||
{@render children()}
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
29
front/src/lib/components/dark-mode-toggle.svelte
Normal file
29
front/src/lib/components/dark-mode-toggle.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { Moon, Sun, SunMoon } from "@lucide/svelte";
|
||||
</script>
|
||||
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-square m-1">
|
||||
<SunMoon size={16 }/>
|
||||
</div>
|
||||
<div
|
||||
class="dropdown-content bg-base-300 rounded-box z-1 w-52 p-2 shadow-2xl grid gap-2"
|
||||
>
|
||||
<button
|
||||
data-set-theme="light"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start"
|
||||
aria-label="Light"
|
||||
>
|
||||
<Sun />
|
||||
Light
|
||||
</button>
|
||||
<button
|
||||
data-set-theme="dark"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start"
|
||||
aria-label="Dark"
|
||||
>
|
||||
<Moon />
|
||||
Dark
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
104
front/src/lib/components/index/AnimatedBeam.svelte
Normal file
104
front/src/lib/components/index/AnimatedBeam.svelte
Normal file
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { onMount, tick } from "svelte";
|
||||
|
||||
let {
|
||||
containerRef = $bindable(),
|
||||
class:className = "",
|
||||
fromRef = $bindable(),
|
||||
toRef = $bindable(),
|
||||
curvature = 0,
|
||||
reverse = false, // Include the reverse prop
|
||||
pathColor = "gray",
|
||||
pathWidth = 2,
|
||||
pathOpacity = 0.2,
|
||||
startXOffset = 0,
|
||||
startYOffset = 0,
|
||||
endXOffset = 0,
|
||||
endYOffset = 0,
|
||||
}= $props();
|
||||
|
||||
|
||||
let id = crypto.randomUUID().slice(0, 8);
|
||||
let pathD = $state("");
|
||||
let svgDimensions = { width: 0, height: 0 };
|
||||
|
||||
let updatePath = () => {
|
||||
if (!containerRef || !fromRef || !toRef) {
|
||||
return;
|
||||
}
|
||||
let containerRect = containerRef?.getBoundingClientRect();
|
||||
let rectA = fromRef?.getBoundingClientRect();
|
||||
let rectB = toRef?.getBoundingClientRect();
|
||||
|
||||
let svgWidth = containerRect.width;
|
||||
let svgHeight = containerRect.height;
|
||||
svgDimensions.width = svgWidth;
|
||||
svgDimensions.height = svgHeight;
|
||||
|
||||
let startX =
|
||||
rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
|
||||
let startY =
|
||||
rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
|
||||
let endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
|
||||
let endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
|
||||
|
||||
let controlY = startY - curvature;
|
||||
let d = `M ${startX},${startY} Q ${
|
||||
(startX + endX) / 2
|
||||
},${controlY} ${endX},${endY}`;
|
||||
pathD = d;
|
||||
};
|
||||
onMount(async () => {
|
||||
await tick().then(() => {
|
||||
updatePath();
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
// For all entries, recalculate the path
|
||||
for (let entry of entries) {
|
||||
updatePath();
|
||||
}
|
||||
});
|
||||
|
||||
// Observe the container element
|
||||
if (containerRef) {
|
||||
resizeObserver.observe(containerRef);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svg
|
||||
fill="none"
|
||||
width={svgDimensions.width}
|
||||
height={svgDimensions.height}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={cn(
|
||||
"pointer-events-none absolute left-0 top-0 transform-gpu stroke-2 animate-pulse",
|
||||
className,
|
||||
)}
|
||||
viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
|
||||
>
|
||||
<path
|
||||
d={pathD}
|
||||
stroke={pathColor}
|
||||
stroke-width={pathWidth}
|
||||
stroke-opacity={pathOpacity}
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d={pathD}
|
||||
stroke-width={pathWidth}
|
||||
stroke={`url(#${id})`}
|
||||
stroke-opacity="1"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient {id} gradientUnits="userSpaceOnUse" class="transform-gpu">
|
||||
<stop class="[stop-color:var(--color-primary)]" stop-opacity="0"></stop>
|
||||
<stop class="[stop-color:var(--color-primary)]"></stop>
|
||||
<stop offset="32.5%" class="[stop-color:var(--color-primary)]"></stop>
|
||||
<stop offset="100%" class="[stop-color:var(--color-primary)]" stop-opacity="0"
|
||||
></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
125
front/src/lib/components/index/AnimatedBeamMultiple.svelte
Normal file
125
front/src/lib/components/index/AnimatedBeamMultiple.svelte
Normal file
@@ -0,0 +1,125 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { Github, IdCard, Key, User } from "@lucide/svelte";
|
||||
import AnimatedBeam from "./AnimatedBeam.svelte";
|
||||
import Circle from "./Circle.svelte";
|
||||
let containerRef = $state();
|
||||
let div1Ref = $state();
|
||||
let div2Ref = $state();
|
||||
let div3Ref = $state();
|
||||
let div4Ref = $state();
|
||||
let div5Ref = $state();
|
||||
let div6Ref = $state();
|
||||
let div7Ref = $state();
|
||||
let className: any = $state("");
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={containerRef}
|
||||
class={cn("relative flex w-full items-center justify-center ", className)}
|
||||
>
|
||||
<div
|
||||
class="flex h-full w-full flex-row justify-between gap-10 max-w-lg items-center"
|
||||
>
|
||||
<div class="flex flex-col justify-center gap-2">
|
||||
<!-- Div 1 -->
|
||||
<Circle bind:ref={div1Ref}>
|
||||
<div class="tooltip" data-tip="Leak of user's informations">
|
||||
<User />
|
||||
</div>
|
||||
</Circle>
|
||||
<!-- Div 2 -->
|
||||
<Circle bind:ref={div2Ref}>
|
||||
<div class="tooltip" data-tip="Leak of user's passwords">
|
||||
<Key />
|
||||
</div>
|
||||
</Circle>
|
||||
<!-- Div 3 -->
|
||||
<Circle bind:ref={div3Ref}>
|
||||
<div class="tooltip" data-tip="Leak of personal informations">
|
||||
<IdCard />
|
||||
</div>
|
||||
</Circle>
|
||||
<!-- Div 4 -->
|
||||
<Circle bind:ref={div4Ref}>
|
||||
<div class="tooltip" data-tip="Github recon">
|
||||
<Github />
|
||||
</div>
|
||||
</Circle>
|
||||
<!-- Div 5 -->
|
||||
<Circle bind:ref={div5Ref}>
|
||||
<div class="tooltip" data-tip="Google hunt">
|
||||
<svg
|
||||
width="16"
|
||||
viewBox="0 0 256 262"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
><path
|
||||
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
|
||||
fill="#fff"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
</Circle>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<!-- Div 6 -->
|
||||
<Circle bind:ref={div6Ref}>
|
||||
<div class="tooltip" data-tip="Your eleakxir backend">
|
||||
<svg
|
||||
width={24}
|
||||
viewBox="0 0 141 205"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={cn("fill-primary", className)}
|
||||
>
|
||||
<path
|
||||
d="M69.7444 0C84.49 25.1637 100.559 49.4708 117.95 72.9219C126.451 85.0149 133.113 98.1035 137.934 112.188C144.168 135.735 140.195 157.355 126.014 177.046C109.938 196.591 89.1945 205.53 63.7844 203.865C36.717 200.867 17.4935 187.019 6.11353 162.321C-1.9191 142.522 -2.03583 122.655 5.76294 102.722C9.71019 93.7604 14.3849 85.2295 19.7864 77.1289C35.0593 56.0531 49.5504 34.4338 63.259 12.2705C65.6086 8.27249 67.7699 4.18207 69.7444 0ZM100.596 81.3359C102.957 92.649 102.198 103.751 98.3176 114.642C93.9276 124.99 87.7338 134.105 79.7366 141.987C77.6951 144.434 75.8254 147.005 74.1272 149.7C70.5033 155.43 68.5745 161.682 68.342 168.456C68.1692 175.079 70.6236 180.455 75.7043 184.583C89.1062 183.345 100.267 177.678 109.186 167.58C123.101 149.518 125.672 129.885 116.899 108.682C112.25 99.0223 106.815 89.9071 100.596 81.3359ZM70.095 36.4609C59.5617 53.4793 48.4018 70.0738 36.6145 86.2441C30.2619 94.8335 25.2366 104.183 21.5393 114.291C15.2642 135.159 19.2377 153.74 33.4592 170.034C39.7381 176.533 47.2756 180.798 56.0715 182.83C55.7506 182.699 55.4583 182.524 55.1956 182.305C40.6021 163.32 39.6671 143.687 52.3909 123.406C58.721 114.622 64.9149 105.74 70.9719 96.7617C72.559 94.0549 73.9614 91.2501 75.179 88.3477C81.8825 70.1929 80.1876 52.8971 70.095 36.4609Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</Circle>
|
||||
</div>
|
||||
<div class="flex flec-col justify-center">
|
||||
<!-- Div 7 -->
|
||||
<Circle bind:ref={div7Ref}>
|
||||
<div class="tooltip" data-tip="This web client">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-monitor-icon lucide-monitor"
|
||||
><rect width="20" height="14" x="2" y="3" rx="2" /><line
|
||||
x1="8"
|
||||
x2="16"
|
||||
y1="21"
|
||||
y2="21"
|
||||
/><line x1="12" x2="12" y1="17" y2="21" /></svg
|
||||
>
|
||||
</div>
|
||||
</Circle>
|
||||
</div>
|
||||
</div>
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div1Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div2Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div3Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div4Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div5Ref} bind:toRef={div6Ref} />
|
||||
<AnimatedBeam bind:containerRef bind:fromRef={div6Ref} bind:toRef={div7Ref} />
|
||||
</div>
|
||||
19
front/src/lib/components/index/Circle.svelte
Normal file
19
front/src/lib/components/index/Circle.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
let { children, label ="", ref=$bindable()} = $props();
|
||||
let className: any = $state("");
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class="tooltip z-10" data-tip={label}>
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"bg-base-100 hover:bg-base-200 border-base-200 z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 p-3 transition-all duration-200 cursor-pointer",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
115
front/src/lib/components/index/search/datawells.svelte
Normal file
115
front/src/lib/components/index/search/datawells.svelte
Normal file
@@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import type { Dataleak } from "$src/lib/types";
|
||||
import { Replace, Search } from "@lucide/svelte";
|
||||
|
||||
let {
|
||||
dataleaks,
|
||||
perPage = 5,
|
||||
showColumns = false,
|
||||
}: {
|
||||
dataleaks: Dataleak[];
|
||||
perPage?: number;
|
||||
showColumns?: boolean;
|
||||
} = $props();
|
||||
|
||||
let page = $state(1);
|
||||
let filter = $state("");
|
||||
let filteredDataleaks = $state<Dataleak[]>(dataleaks);
|
||||
let paginatedDataleaks = $state<Dataleak[]>([]);
|
||||
let totalPages = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
if (filter.trim() === "") {
|
||||
filteredDataleaks = dataleaks;
|
||||
} else {
|
||||
const lowerFilter = filter.toLowerCase();
|
||||
filteredDataleaks = dataleaks.filter((item) =>
|
||||
item.Name.toLowerCase().includes(lowerFilter),
|
||||
);
|
||||
}
|
||||
page = 1;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (filteredDataleaks) {
|
||||
totalPages = Math.ceil(filteredDataleaks.length / perPage);
|
||||
const start = (page - 1) * perPage;
|
||||
const end = start + perPage;
|
||||
paginatedDataleaks = filteredDataleaks.slice(start, end);
|
||||
if (page > totalPages) {
|
||||
page = totalPages > 0 ? totalPages : 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function previousPage() {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (page < totalPages) {
|
||||
page++;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<label class="input input-xs w-full">
|
||||
<Search size={12} />
|
||||
<input class="grow" placeholder="Filter" bind:value={filter} />
|
||||
</label>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Number of rows</th>
|
||||
{#if showColumns}
|
||||
<th>Columns</th>
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if paginatedDataleaks.length > 0}
|
||||
{#each paginatedDataleaks as item}
|
||||
<tr class="hover:bg-base-300">
|
||||
<th>
|
||||
{item.Name}
|
||||
</th>
|
||||
<td>{item.Length.toLocaleString("fr")}</td>
|
||||
{#if showColumns}
|
||||
<td class="capitalize">
|
||||
{item.Columns.map((col) => col.replace(/_/g, " ")).join(", ")}
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr class="hover:bg-base-300">
|
||||
<td colspan="2" class="text-center leading-9"
|
||||
><span class="text-3xl">(·.·)</span><br />No data wells found</td
|
||||
>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{#if totalPages > 1}
|
||||
<div class="join m-auto mt-5">
|
||||
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
|
||||
>«</button
|
||||
>
|
||||
<button class="join-item btn">Page {page} / {totalPages}</button>
|
||||
<button
|
||||
class="join-item btn"
|
||||
onclick={nextPage}
|
||||
disabled={page === totalPages}>»</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
130
front/src/lib/components/index/search/history.svelte
Normal file
130
front/src/lib/components/index/search/history.svelte
Normal file
@@ -0,0 +1,130 @@
|
||||
<script lang="ts">
|
||||
import type { History } from "$src/lib/types";
|
||||
import { formatDate } from "$src/lib/utils";
|
||||
import { Search } from "@lucide/svelte";
|
||||
import { navigate, p } from "sv-router/generated";
|
||||
|
||||
let { history, perPage = 5 }: { history: History; perPage?: number } =
|
||||
$props();
|
||||
|
||||
let page = $state(1);
|
||||
let filter = $state("");
|
||||
let filteredHistory = $state<History>(history);
|
||||
let paginatedHistory = $state<History>([]);
|
||||
let totalPages = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
if (filter.trim() === "") {
|
||||
filteredHistory = history;
|
||||
} else {
|
||||
const lowerFilter = filter.toLowerCase();
|
||||
filteredHistory = history.filter((item) =>
|
||||
item.Query.Text.toLowerCase().includes(lowerFilter),
|
||||
);
|
||||
}
|
||||
page = 1;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (filteredHistory) {
|
||||
totalPages = Math.ceil(filteredHistory.length / perPage);
|
||||
const start = (page - 1) * perPage;
|
||||
const end = start + perPage;
|
||||
paginatedHistory = filteredHistory.slice(start, end);
|
||||
if (page > totalPages) {
|
||||
page = totalPages > 0 ? totalPages : 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function previousPage() {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (page < totalPages) {
|
||||
page++;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<label class="input input-xs w-full">
|
||||
<Search size={12} />
|
||||
<input class="grow" placeholder="Filter" bind:value={filter} />
|
||||
</label>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Query</th>
|
||||
<th>Results</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if paginatedHistory.length > 0}
|
||||
{#each paginatedHistory as item}
|
||||
<tr class="hover:bg-base-300">
|
||||
<th>
|
||||
<button
|
||||
onclick={() => {
|
||||
navigate(`/search/:id`, { params: { id: item.Id } });
|
||||
}}
|
||||
class="btn btn-link p-0 no-underline text-base-content"
|
||||
>
|
||||
{item.Query.Text}
|
||||
</button>
|
||||
</th>
|
||||
<td>{item.Results}</td>
|
||||
<td
|
||||
><div
|
||||
class="badge badge-xs"
|
||||
class:badge-success={item.Status === "completed"}
|
||||
class:badge-warning={item.Status === "pending"}
|
||||
>
|
||||
{item.Status}
|
||||
</div></td
|
||||
>
|
||||
<td>{formatDate(item.Date)}</td>
|
||||
<td
|
||||
onclick={() => {
|
||||
navigate(`/search/:id`, { params: { id: item.Id } });
|
||||
}}
|
||||
><button class="btn btn-xs btn-square"
|
||||
><Search size={11} /></button
|
||||
></td
|
||||
>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr class="hover:bg-base-300">
|
||||
<td colspan="5" class="text-center leading-9"
|
||||
><span class="text-3xl">(·.·)</span><br />No history found</td
|
||||
>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{#if totalPages > 1}
|
||||
<div class="join m-auto mt-5">
|
||||
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
|
||||
>«</button
|
||||
>
|
||||
<button class="join-item btn">Page {page} / {totalPages}</button>
|
||||
<button
|
||||
class="join-item btn"
|
||||
onclick={nextPage}
|
||||
disabled={page === totalPages}>»</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
46
front/src/lib/components/index/search/howToSearch.svelte
Normal file
46
front/src/lib/components/index/search/howToSearch.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts"></script>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
Eleakxir's search engine is designed to be both fast and flexible, letting
|
||||
you find what you need in multiple ways.
|
||||
</p>
|
||||
|
||||
<h3 class="h3 mt-4 mb-2">Search Modes</h3>
|
||||
|
||||
<p>
|
||||
<span class="text-primary font-semibold">All:</span> This is the default mode.
|
||||
It searches for your query across a set of standard columns like email, username,
|
||||
and phone number. This is the fastest and most efficient way to find a specific
|
||||
user or account.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="text-primary font-semibold">Specific column:</span>
|
||||
This mode lets you choose a specific column to search within, such as email or
|
||||
username. It's useful when you know exactly where the data you're looking for
|
||||
is stored.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="text-primary font-semibold">Full Text:</span> This mode combines
|
||||
all available columns into a single, large text field and searches within it.
|
||||
It's great for finding data that might be in an unexpected column, but it's way
|
||||
slower.
|
||||
</p>
|
||||
|
||||
<h3 class="h3 mt-4 mb-2">Query Matching</h3>
|
||||
<p>
|
||||
<span class="text-primary font-semibold">Standard Search:</span> By default,
|
||||
Eleakxir uses a "fuzzy" search. This means it will find results where your search
|
||||
terms are part of a larger string. For example, searching for 1234 would find
|
||||
john.doe@1234.com.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="text-primary font-semibold">Exact Match:</span> When you enable
|
||||
"Exact Match," the search will only return results where the data in a column
|
||||
is an exact match for your search term. This is useful for finding specific,
|
||||
unique values.
|
||||
</p>
|
||||
</div>
|
||||
313
front/src/lib/components/index/search/id/githubResult.svelte
Normal file
313
front/src/lib/components/index/search/id/githubResult.svelte
Normal file
@@ -0,0 +1,313 @@
|
||||
<script lang="ts">
|
||||
import Accordion from "$src/lib/components/accordion.svelte";
|
||||
import Table from "$src/lib/components/table.svelte";
|
||||
import type { GithubResult } from "$src/lib/types";
|
||||
import { FlattenObject } from "$src/lib/utils";
|
||||
import {
|
||||
Building,
|
||||
ExternalLink,
|
||||
GitCommitVertical,
|
||||
Handshake,
|
||||
Key,
|
||||
Mail,
|
||||
UserRoundPen,
|
||||
} from "@lucide/svelte";
|
||||
const { githubResult }: { githubResult: GithubResult } = $props();
|
||||
</script>
|
||||
|
||||
{#if githubResult.UsernameResult}
|
||||
<div class="w-full">
|
||||
<div class="flex flex-wrap gap-5">
|
||||
<div class="avatar">
|
||||
<div class="w-24 h-24 rounded-xl">
|
||||
<img
|
||||
src={githubResult.UsernameResult.User.AvatarURL}
|
||||
alt="Avatar of {githubResult.UsernameResult.User.Username}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col">
|
||||
<h3 class="h3">{githubResult.UsernameResult.User.Name}</h3>
|
||||
<p class="text-base-content/60">
|
||||
@{githubResult.UsernameResult.User.Username}
|
||||
</p>
|
||||
</div>
|
||||
<p class="max-w-sm">{githubResult.UsernameResult.User.Bio}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-border border-neutral shadow my-8">
|
||||
<div class="grid">
|
||||
<Table
|
||||
row={{
|
||||
publicRepos: githubResult.UsernameResult.User.PublicRepos,
|
||||
followers: githubResult.UsernameResult.User.Followers,
|
||||
following: githubResult.UsernameResult.User.Following,
|
||||
createdAt: new Date(
|
||||
githubResult.UsernameResult.User.CreatedAt,
|
||||
).toLocaleDateString(),
|
||||
email: githubResult.UsernameResult.User.Email,
|
||||
location: githubResult.UsernameResult.User.Location,
|
||||
company: githubResult.UsernameResult.User.Company,
|
||||
url:
|
||||
"https://github.com/" + githubResult.UsernameResult.User.Username,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if githubResult.UsernameResult.Socials && githubResult.UsernameResult.Socials.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Social Links</h4>
|
||||
<ul class="flex gap-4 flex-col mt-4 mb-6">
|
||||
{#each githubResult.UsernameResult.Socials as social}
|
||||
<a href={social.URL} target="_blank" rel="noopener noreferrer">
|
||||
<div class="badge bg-base-300">
|
||||
<ExternalLink size={12} />
|
||||
{social.URL}
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.CloseFriends && githubResult.UsernameResult.CloseFriends.length > 0}
|
||||
<div class="mt-4">
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={Handshake}
|
||||
title={"Close Friends"}
|
||||
subtitle={ githubResult.UsernameResult.CloseFriends.length + " close friends found"}
|
||||
>
|
||||
<Table
|
||||
row={githubResult.UsernameResult.CloseFriends}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.Orgs && githubResult.UsernameResult.Orgs.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Organizations</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={Building}
|
||||
title="Organizations"
|
||||
subtitle={"Found " + githubResult.UsernameResult.Orgs.length + " organizations"}
|
||||
>
|
||||
<Table
|
||||
row={githubResult.UsernameResult.Orgs}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.Commits && githubResult.UsernameResult.Commits.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Commits</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.UsernameResult.Commits as commit}
|
||||
<Accordion
|
||||
icon={GitCommitVertical}
|
||||
title={commit.Name + " <" + commit.Email + ">"}
|
||||
subtitle={"Occurrences: " + commit.Occurrences}
|
||||
>
|
||||
<Table
|
||||
row={{
|
||||
name: commit.Name,
|
||||
email: commit.Email,
|
||||
url: "https://github.com/" + commit.FirstFoundIn,
|
||||
occurrences: commit.Occurrences,
|
||||
}}
|
||||
/>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.SshKeys && githubResult.UsernameResult.SshKeys.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">SSH Keys</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.UsernameResult.SshKeys as key}
|
||||
<Accordion
|
||||
icon={Key}
|
||||
title={"Created At: " +
|
||||
new Date(key.CreatedAt).toLocaleDateString()}
|
||||
subtitle={"Last Used: " +
|
||||
(key.LastUsed !== "0001-01-01 00:00:00 +0000 UTC"
|
||||
? new Date(key.LastUsed).toLocaleDateString()
|
||||
: "Never")}
|
||||
>
|
||||
<pre class="overflow-x-auto p-2 bg-base-200 rounded"><code
|
||||
class="break-all">{key.Key}</code
|
||||
></pre>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.SshSigningKeys && githubResult.UsernameResult.SshSigningKeys.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">SSH Signing Keys</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.UsernameResult.SshSigningKeys as key}
|
||||
<Accordion
|
||||
icon={Key}
|
||||
title={key.Title}
|
||||
subtitle={"Created At: " + key.CreatedAt}
|
||||
>
|
||||
<pre class="overflow-x-auto p-2 bg-base-200 rounded"><code
|
||||
class="break-all">{key.Key}</code
|
||||
></pre>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.GpgKeys && githubResult.UsernameResult.GpgKeys.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">GPG Keys</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.UsernameResult.GpgKeys as key}
|
||||
<Accordion
|
||||
icon={Key}
|
||||
title={key.Emails && key.Emails.length > 0 ? key.Emails[0].Email : key.KeyID}
|
||||
subtitle={"Created At: " + key.CreatedAt}
|
||||
>
|
||||
<Table
|
||||
row={FlattenObject(key)}
|
||||
/>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.DeepScan}
|
||||
{#if githubResult.UsernameResult.DeepScan.Authors && githubResult.UsernameResult.DeepScan.Authors.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Deep scan authors</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={UserRoundPen}
|
||||
title="Authors"
|
||||
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Authors.length + " authors"
|
||||
}
|
||||
>
|
||||
<Table
|
||||
row={githubResult.UsernameResult.DeepScan.Authors}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.DeepScan.Emails && githubResult.UsernameResult.DeepScan.Emails.length > 0}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Deep scan emails</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={Mail}
|
||||
title="Emails"
|
||||
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Emails.length + " emails"
|
||||
}
|
||||
>
|
||||
<Table
|
||||
row={githubResult.UsernameResult.DeepScan.Emails}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.UsernameResult.DeepScan.Secrets && githubResult.UsernameResult.DeepScan.Secrets.length > 0}
|
||||
{@const flattenedSecrets = githubResult.UsernameResult.DeepScan.Secrets.map(FlattenObject)}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Deep scan secrets</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
<Accordion
|
||||
icon={Mail}
|
||||
title="Secrets"
|
||||
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Secrets.length + " secrets"
|
||||
}
|
||||
>
|
||||
<Table
|
||||
row={flattenedSecrets}
|
||||
/>
|
||||
</Accordion>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if githubResult.EmailResult}
|
||||
<div class="w-full">
|
||||
{#if githubResult.EmailResult.Spoofing}
|
||||
<h4 class="h4 mb-4">From spoofing</h4>
|
||||
<div class="flex flex-wrap gap-5">
|
||||
<div class="avatar">
|
||||
<div class="w-24 h-24 rounded-xl">
|
||||
<img
|
||||
src={githubResult.EmailResult.Spoofing.AvatarURL}
|
||||
alt="Avatar of {githubResult.EmailResult.Spoofing.Username}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h4 class="h4">@{githubResult.EmailResult.Spoofing.Username}</h4>
|
||||
{#if githubResult.EmailResult.Spoofing.Name}
|
||||
<p>
|
||||
<strong>Name:</strong>
|
||||
{githubResult.EmailResult.Spoofing.Name}
|
||||
</p>
|
||||
{/if}
|
||||
{#if githubResult.EmailResult.Spoofing.Email}
|
||||
<p>
|
||||
<strong>Public email:</strong>
|
||||
{githubResult.EmailResult.Spoofing.Email}
|
||||
</p>
|
||||
{/if}
|
||||
{#if githubResult.EmailResult.Target}
|
||||
<p class="break-all">
|
||||
<strong>Primary email:</strong>
|
||||
{githubResult.EmailResult.Target}
|
||||
</p>
|
||||
{/if}
|
||||
<a
|
||||
href={githubResult.EmailResult.Spoofing.Url}
|
||||
class="link link-primary flex gap-2 items-center"
|
||||
target="_blank"
|
||||
>
|
||||
{githubResult.EmailResult.Spoofing.Url}
|
||||
<ExternalLink size={12} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if githubResult.EmailResult.Commits}
|
||||
<div class="mt-4">
|
||||
<h4 class="h4 mb-2">Commits</h4>
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each githubResult.EmailResult.Commits as commit}
|
||||
<Accordion
|
||||
icon={GitCommitVertical}
|
||||
title={commit.Username && commit.Username !== ""
|
||||
? commit.Name + " (@" + commit.Username + ")"
|
||||
: commit.Name}
|
||||
subtitle={"Occurrences: " + commit.Occurrences}
|
||||
>
|
||||
<Table
|
||||
row={{
|
||||
name: commit.Name,
|
||||
username: commit.Username,
|
||||
email: commit.Email,
|
||||
first_found_in: commit.FirstFoundIn,
|
||||
occurrences: commit.Occurrences,
|
||||
}}
|
||||
/>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
100
front/src/lib/components/index/search/id/row.svelte
Normal file
100
front/src/lib/components/index/search/id/row.svelte
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import Table from "$src/lib/components/table.svelte";
|
||||
import { ChevronDown, ChevronUp, Database, Key, Mail } from "@lucide/svelte";
|
||||
|
||||
const { row }: { row: Record<string, string> } = $props();
|
||||
|
||||
let isOpen = $state<boolean>(false);
|
||||
|
||||
function getDomain(dataleakName: string) {
|
||||
const firstPart = dataleakName.split(" ")[0].toLowerCase();
|
||||
const domainRegex =
|
||||
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/;
|
||||
if (domainRegex.test(firstPart)) {
|
||||
return firstPart;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getHighlightedContent(row: Record<string, string>): string {
|
||||
const prioritizedKeys = [
|
||||
"email",
|
||||
"username",
|
||||
"full_name",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"phone",
|
||||
"password",
|
||||
"address",
|
||||
];
|
||||
|
||||
for (const key of prioritizedKeys) {
|
||||
if (row[key]) {
|
||||
return row[key];
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in row) {
|
||||
if (row[key]) {
|
||||
return row[key];
|
||||
}
|
||||
}
|
||||
|
||||
return "No content";
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="list-row hover:bg-base-300/75 text-left"
|
||||
class:bg-base-300={isOpen}
|
||||
class:rounded-b-none={isOpen}
|
||||
onclick={() => {
|
||||
isOpen = !isOpen;
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{#if getDomain(row["source"])}
|
||||
<img
|
||||
src="https://icons.duckduckgo.com/ip3/{getDomain(row['source'])}.ico"
|
||||
class="size-10 rounded-box bg-neutral"
|
||||
alt="Favicon de {getDomain(row['source'])}"
|
||||
/>
|
||||
{:else if row["password"] !== null}
|
||||
<div
|
||||
class="size-10 rounded-box bg-neutral items-center justify-center flex"
|
||||
>
|
||||
<Key />
|
||||
</div>
|
||||
{:else if row["email"] !== null}
|
||||
<div
|
||||
class="size-10 rounded-box bg-neutral items-center justify-center flex"
|
||||
>
|
||||
<Mail />
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="size-10 rounded-box bg-neutral items-center justify-center flex"
|
||||
>
|
||||
<Database />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<div>{getHighlightedContent(row)}</div>
|
||||
<div class="text-xs uppercase font-semibold opacity-60">
|
||||
{row["source"]}
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn btn-square btn-ghost">
|
||||
{#if isOpen}
|
||||
<ChevronUp size={12} />
|
||||
{:else}
|
||||
<ChevronDown size={12} />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{#if isOpen}
|
||||
<li class="list-row flex bg-base-200 rounded-t-none mb-2">
|
||||
<Table {row} />
|
||||
</li>
|
||||
{/if}
|
||||
72
front/src/lib/components/index/search/id/rows.svelte
Normal file
72
front/src/lib/components/index/search/id/rows.svelte
Normal file
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import type { Result } from "$src/lib/types";
|
||||
import Row from "./row.svelte";
|
||||
|
||||
const { result }: { result: Result } = $props();
|
||||
|
||||
let page = $state(1);
|
||||
let totalPages = $state(0);
|
||||
const perPage = 20;
|
||||
|
||||
let paginated = $state<Record<string, string>[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
if (result && result.LeakResult.Rows) {
|
||||
totalPages = Math.ceil(result.LeakResult.Rows.length / perPage);
|
||||
const start = (page - 1) * perPage;
|
||||
const end = start + perPage;
|
||||
paginated = result.LeakResult.Rows.slice(start, end);
|
||||
if (page > totalPages) {
|
||||
page = totalPages > 0 ? totalPages : 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function goToFirstPage() {
|
||||
page = 1;
|
||||
top.scrollIntoView();
|
||||
}
|
||||
|
||||
function previousPage() {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
top.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (page < totalPages) {
|
||||
page++;
|
||||
top.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
let top: any = $state();
|
||||
</script>
|
||||
|
||||
<div bind:this={top} class="absolute -mt-[100px]"></div>
|
||||
{#if result}
|
||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||
{#each paginated as row (row)}
|
||||
<Row {row} />
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if totalPages > 1}
|
||||
<div class="join m-auto mt-5">
|
||||
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
|
||||
>«</button
|
||||
>
|
||||
<button class="join-item btn" onclick={goToFirstPage}
|
||||
>Page {page} / {totalPages}</button
|
||||
>
|
||||
<button
|
||||
class="join-item btn"
|
||||
onclick={nextPage}
|
||||
disabled={page === totalPages}>»</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
No result
|
||||
{/if}
|
||||
54
front/src/lib/components/index/search/id/stats.svelte
Normal file
54
front/src/lib/components/index/search/id/stats.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import type { Result } from "$src/lib/types";
|
||||
import { formatDate } from "$src/lib/utils";
|
||||
import { BadgeInfo, Clock, File } from "@lucide/svelte";
|
||||
|
||||
const { result }: { result: Result } = $props();
|
||||
|
||||
let nresult = $state(0);
|
||||
$effect(() => {
|
||||
const r = [
|
||||
result.LeakResult.Rows?.length | 0,
|
||||
result.GithubResult.EmailResult?.Commits?.length | 0,
|
||||
result.GithubResult.EmailResult?.Spoofing ? 1 : 0,
|
||||
result.GithubResult.UsernameResult?.Commits?.length | 0,
|
||||
];
|
||||
nresult = r.reduce((a, b) => a + b, 0);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="stats stats-vertical md:stats-horizontal">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<File />
|
||||
</div>
|
||||
<div class="stat-title">Results</div>
|
||||
<div class="stat-value" class:animate-pulse={result.Status === "pending"}>
|
||||
{nresult.toLocaleString("fr")}
|
||||
{#if result.Status === "pending"}
|
||||
<span class="loading loading-dots loading-xs ml-2"></span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<Clock />
|
||||
</div>
|
||||
<div class="stat-title">Date</div>
|
||||
<div class="stat-value">
|
||||
{formatDate(result.Date)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<BadgeInfo />
|
||||
</div>
|
||||
<div class="stat-title">Status</div>
|
||||
<div class="stat-value" class:animate-pulse={result.Status === "pending"}>
|
||||
{result.Status}
|
||||
{#if result.Status === "pending"}
|
||||
<span class="loading loading-dots loading-xs ml-2"></span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
104
front/src/lib/components/index/search/searchbar.svelte
Normal file
104
front/src/lib/components/index/search/searchbar.svelte
Normal file
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { serverPassword, serverUrl } from "$src/lib/stores/server";
|
||||
import { cn } from "$src/lib/utils";
|
||||
import { Equal, EqualNot, Search } from "@lucide/svelte";
|
||||
import axios from "axios";
|
||||
import { navigate } from "sv-router/generated";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
const {
|
||||
initialQuery = "",
|
||||
initialFilter = "all",
|
||||
initialExactMatch = false,
|
||||
}: {
|
||||
initialQuery?: string;
|
||||
initialFilter?: string;
|
||||
initialExactMatch?: boolean;
|
||||
} = $props();
|
||||
|
||||
let filters = [
|
||||
"all",
|
||||
"username",
|
||||
"email",
|
||||
"name",
|
||||
"phone",
|
||||
"url",
|
||||
"password",
|
||||
"password hash",
|
||||
"full_text",
|
||||
];
|
||||
let activeFilter = $state<string>(initialFilter);
|
||||
let query = $state<string>(initialQuery);
|
||||
let exactMatch = $state<boolean>(initialExactMatch);
|
||||
|
||||
function NewSearch() {
|
||||
axios
|
||||
.post(
|
||||
`${$serverUrl}/search`,
|
||||
{ Text: query, Column: activeFilter, ExactMatch: exactMatch },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Password": $serverPassword,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((r) => {
|
||||
const id = r.data.Id;
|
||||
window.location.href = `/search/${id}`;
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.response.data.Error !== undefined) {
|
||||
toast.error(e.response.data.Error);
|
||||
} else {
|
||||
toast.error("An error occurred");
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex gap-5 flex-col">
|
||||
<div
|
||||
class="flex gap-3 justify-start items-center w-full overflow-y-hidden overflow-x-auto"
|
||||
>
|
||||
{#each filters as filter}
|
||||
<button
|
||||
class={cn(
|
||||
"btn btn-md capitalize",
|
||||
activeFilter === filter
|
||||
? "btn-primary"
|
||||
: "btn-ghost btn-neutral text-base-content/80 hover:text-neutral-content",
|
||||
)}
|
||||
onclick={() => (activeFilter = filter)}>{filter.replace("_", " ")}</button
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="join w-full"
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
NewSearch();
|
||||
}}
|
||||
>
|
||||
<label class="grow input input-xl input-primary join-item w-full">
|
||||
<Search size={16} />
|
||||
<input
|
||||
class="grow input-xl"
|
||||
type="text"
|
||||
bind:value={query}
|
||||
placeholder="Search..."
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="tooltip" data-tip="Exact Match">
|
||||
<label class="toggle text-base-content toggle-xs">
|
||||
<input type="checkbox" bind:checked={exactMatch} />
|
||||
<EqualNot aria-label="disable" size={12} />
|
||||
<Equal aria-label="enabled" size={12} />
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
<button class="btn btn-primary btn-xl join-item">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
83
front/src/lib/components/index/search/services.svelte
Normal file
83
front/src/lib/components/index/search/services.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import type { Server } from "$src/lib/types";
|
||||
|
||||
let { serverInfo }: { serverInfo: Server } = $props();
|
||||
</script>
|
||||
|
||||
<div class="my-4">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Service</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="hover:bg-base-300">
|
||||
<th> Data wells lookup </th>
|
||||
<td>
|
||||
{#if serverInfo.Dataleaks.length !== 0}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-success"></div>
|
||||
<div class="status status-success"></div>
|
||||
</div>
|
||||
Active
|
||||
{:else}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-error animate-ping"></div>
|
||||
<div class="status status-error"></div>
|
||||
</div>
|
||||
Inactive
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-base-300">
|
||||
<th class="flex flex-wrap gap-2 items-center">
|
||||
Github recon
|
||||
{#if serverInfo.Settings.GithubTokenLoaded === true}
|
||||
<div class="badge badge-xs badge-neutral">Token</div>
|
||||
{/if}
|
||||
{#if serverInfo.Settings.GithubDeepMode === true}
|
||||
<div class="badge badge-xs badge-neutral">Deep Mode</div>
|
||||
{/if}
|
||||
</th>
|
||||
<td>
|
||||
{#if serverInfo.Settings.GithubRecon === true}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-success"></div>
|
||||
<div class="status status-success"></div>
|
||||
</div>
|
||||
Active
|
||||
{:else}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-error animate-ping"></div>
|
||||
<div class="status status-error"></div>
|
||||
</div>
|
||||
Inactive
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-base-300">
|
||||
<th>Google hunt</th>
|
||||
<td>
|
||||
{#if serverInfo.Settings.GithubRecon === true}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-success"></div>
|
||||
<div class="status status-success"></div>
|
||||
</div>
|
||||
Active
|
||||
{:else}
|
||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||
<div class="status status-error animate-ping"></div>
|
||||
<div class="status status-error"></div>
|
||||
</div>
|
||||
Inactive
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
46
front/src/lib/components/index/search/stats.svelte
Normal file
46
front/src/lib/components/index/search/stats.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import type { Server } from "$src/lib/types";
|
||||
import { Database, File, Save } from "@lucide/svelte";
|
||||
|
||||
const { serverInfo }: { serverInfo: Server | null } = $props();
|
||||
|
||||
function mbToGb(mb: number): number {
|
||||
return Math.round((mb / 1024) * 100) / 100;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="stats stats-vertical md:stats-horizontal">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<File />
|
||||
</div>
|
||||
<div class="stat-title">Rows available</div>
|
||||
<div class="stat-value">
|
||||
{serverInfo?.TotalRows
|
||||
? serverInfo.TotalRows.toLocaleString("fr")
|
||||
: "-- --- --- ---"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<Database />
|
||||
</div>
|
||||
<div class="stat-title">Data wells available</div>
|
||||
<div class="stat-value">
|
||||
{serverInfo?.TotalDataleaks
|
||||
? serverInfo.TotalDataleaks.toLocaleString("fr")
|
||||
: "---"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<Save />
|
||||
</div>
|
||||
<div class="stat-title">Storage used</div>
|
||||
<div class="stat-value">
|
||||
{serverInfo?.TotalSize
|
||||
? mbToGb(serverInfo.TotalSize).toLocaleString("fr") + " Gb"
|
||||
: "--- Gb"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
16
front/src/lib/components/logo.svelte
Normal file
16
front/src/lib/components/logo.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
const { class: className = "", size = 25 } = $props();
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={size}
|
||||
viewBox="0 0 141 205"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={cn("fill-primary", className)}
|
||||
>
|
||||
<path
|
||||
d="M69.7444 0C84.49 25.1637 100.559 49.4708 117.95 72.9219C126.451 85.0149 133.113 98.1035 137.934 112.188C144.168 135.735 140.195 157.355 126.014 177.046C109.938 196.591 89.1945 205.53 63.7844 203.865C36.717 200.867 17.4935 187.019 6.11353 162.321C-1.9191 142.522 -2.03583 122.655 5.76294 102.722C9.71019 93.7604 14.3849 85.2295 19.7864 77.1289C35.0593 56.0531 49.5504 34.4338 63.259 12.2705C65.6086 8.27249 67.7699 4.18207 69.7444 0ZM100.596 81.3359C102.957 92.649 102.198 103.751 98.3176 114.642C93.9276 124.99 87.7338 134.105 79.7366 141.987C77.6951 144.434 75.8254 147.005 74.1272 149.7C70.5033 155.43 68.5745 161.682 68.342 168.456C68.1692 175.079 70.6236 180.455 75.7043 184.583C89.1062 183.345 100.267 177.678 109.186 167.58C123.101 149.518 125.672 129.885 116.899 108.682C112.25 99.0223 106.815 89.9071 100.596 81.3359ZM70.095 36.4609C59.5617 53.4793 48.4018 70.0738 36.6145 86.2441C30.2619 94.8335 25.2366 104.183 21.5393 114.291C15.2642 135.159 19.2377 153.74 33.4592 170.034C39.7381 176.533 47.2756 180.798 56.0715 182.83C55.7506 182.699 55.4583 182.524 55.1956 182.305C40.6021 163.32 39.6671 143.687 52.3909 123.406C58.721 114.622 64.9149 105.74 70.9719 96.7617C72.559 94.0549 73.9614 91.2501 75.179 88.3477C81.8825 70.1929 80.1876 52.8971 70.095 36.4609Z"
|
||||
/>
|
||||
</svg>
|
||||
144
front/src/lib/components/server-dialog.svelte
Normal file
144
front/src/lib/components/server-dialog.svelte
Normal file
@@ -0,0 +1,144 @@
|
||||
<script lang="ts">
|
||||
import { Key, Link, RefreshCw, Server } from "@lucide/svelte";
|
||||
import { cn } from "../utils";
|
||||
import { serverUrl, serverPassword } from "$lib/stores/server";
|
||||
import { toast } from "svelte-sonner";
|
||||
import axios from "axios";
|
||||
|
||||
let { text = "", class: className = "" } = $props();
|
||||
|
||||
let isModalOpen = $state(false);
|
||||
let needToTest = $state(true);
|
||||
|
||||
let url = $state($serverUrl || "https://");
|
||||
let password = $state($serverPassword);
|
||||
|
||||
let working = $state<boolean | null>(null);
|
||||
|
||||
function save() {
|
||||
isModalOpen = false;
|
||||
$serverUrl = url;
|
||||
$serverPassword = password;
|
||||
toast.success("Server settings saved!");
|
||||
}
|
||||
|
||||
function test() {
|
||||
axios
|
||||
.get(`${url}/`)
|
||||
.then(() => {
|
||||
toast.success("Server is working!");
|
||||
needToTest = false;
|
||||
working = true;
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Server is not working!");
|
||||
needToTest = true;
|
||||
working = false;
|
||||
});
|
||||
}
|
||||
|
||||
function reset() {
|
||||
$serverUrl = "";
|
||||
$serverPassword = "";
|
||||
url = "https://";
|
||||
password = "";
|
||||
needToTest = true;
|
||||
working = null;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (isModalOpen) {
|
||||
url = $serverUrl || "https://";
|
||||
needToTest = true;
|
||||
working = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="indicator">
|
||||
<span class="indicator-item">
|
||||
<div class="inline-grid *:[grid-area:1/1]">
|
||||
{#if $serverUrl !== ""}
|
||||
<div class="status status-success"></div>
|
||||
{:else}
|
||||
<div class="status status-error animate-ping"></div>
|
||||
<div class="status status-error"></div>
|
||||
{/if}
|
||||
</div>
|
||||
</span>
|
||||
<button
|
||||
onclick={() => {
|
||||
isModalOpen = !isModalOpen;
|
||||
}}
|
||||
class={cn(className, "btn btn-ghost btn-primary")}
|
||||
>
|
||||
<Server size={16} />
|
||||
{text}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<dialog
|
||||
class="modal modal-bottom sm:modal-middle"
|
||||
class:modal-open={isModalOpen}
|
||||
>
|
||||
<div class="modal-box">
|
||||
<form method="dialog">
|
||||
<button
|
||||
onclick={() => (isModalOpen = false)}
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button
|
||||
>
|
||||
</form>
|
||||
|
||||
<div class="flex flex-col gap-5">
|
||||
<div>
|
||||
<h2 class="card-title">Connect to your server</h2>
|
||||
<p>
|
||||
You can connect to your own Eleakxir server by providing the server
|
||||
URL and an optional password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label
|
||||
class="input w-full"
|
||||
class:input-error={working === false}
|
||||
class:input-success={working === true}
|
||||
>
|
||||
<Link size={16} />
|
||||
<input
|
||||
class="grow"
|
||||
type="url"
|
||||
required
|
||||
placeholder="https://"
|
||||
bind:value={url}
|
||||
/>
|
||||
|
||||
<button class="btn btn-xs btn-square btn-ghost" onclick={reset}
|
||||
><RefreshCw size={8} /></button
|
||||
>
|
||||
</label>
|
||||
|
||||
<label class="input w-full">
|
||||
<Key />
|
||||
<input
|
||||
type="password"
|
||||
class="grow"
|
||||
placeholder="Password"
|
||||
bind:value={password}
|
||||
/>
|
||||
<span class="badge badge-neutral badge-xs">Optional</span>
|
||||
</label>
|
||||
|
||||
<div class="card-actions flex gap-2">
|
||||
<button onclick={test} class="btn btn-primary btn-outline">Test</button>
|
||||
<button
|
||||
onclick={save}
|
||||
disabled={needToTest}
|
||||
class="btn btn-primary grow">Save</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button onclick={() => (isModalOpen = false)}>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
79
front/src/lib/components/table.svelte
Normal file
79
front/src/lib/components/table.svelte
Normal file
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { ExternalLink } from "@lucide/svelte";
|
||||
|
||||
const {
|
||||
row,
|
||||
}: { row: Record<string, string> | Array<Record<string, string>> } = $props();
|
||||
</script>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
{#if Array.isArray(row) && row.length !== 0}
|
||||
{@const head = Object.entries(row[0])}
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
{#each head as [key, _]}
|
||||
<th
|
||||
class="text-xs whitespace-nowrap font-semibold opacity-60 capitalize"
|
||||
>
|
||||
{key}
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each row as item}
|
||||
<tr>
|
||||
{#each Object.entries(item) as [key, value]}
|
||||
<th class="text-xs whitespace-nowrap font-semibold opacity-60">
|
||||
{#if key.toLowerCase() === "url" && value !== "" && value !== null}
|
||||
<a
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary gap-2 items-center flex"
|
||||
>
|
||||
{value}
|
||||
<ExternalLink size={12} />
|
||||
</a>
|
||||
{:else}
|
||||
{value}
|
||||
{/if}
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
{:else}
|
||||
<tbody>
|
||||
{#each Object.entries(row) as [key, value]}
|
||||
{#if key !== "source" && value !== "" && value !== null}
|
||||
<tr class="">
|
||||
<th
|
||||
class="text-xs whitespace-nowrap font-semibold opacity-60 capitalize"
|
||||
>{key.replace(/_/g, " ")}</th
|
||||
>
|
||||
|
||||
<td class="w-fit overflow-x-auto whitespace-nowrap">
|
||||
{#if key.toLowerCase() === "url"}
|
||||
<a
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary gap-2 items-center flex"
|
||||
>
|
||||
{value}
|
||||
<ExternalLink size={12} />
|
||||
</a>
|
||||
{:else}
|
||||
{value}
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
{/each}
|
||||
</tbody>
|
||||
{/if}
|
||||
</table>
|
||||
</div>
|
||||
30
front/src/lib/navigation/sidebar-menu-item.svelte
Normal file
30
front/src/lib/navigation/sidebar-menu-item.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { isActiveLink } from 'sv-router';
|
||||
let { item } = $props();
|
||||
import Self from './sidebar-menu-item.svelte';
|
||||
</script>
|
||||
|
||||
<li>
|
||||
{#if item.items}
|
||||
<details open>
|
||||
<summary class="flex gap-2 items-center">
|
||||
{#if item.icon}
|
||||
<item.icon size={16} />
|
||||
{/if}
|
||||
{item.title}
|
||||
</summary>
|
||||
<ul>
|
||||
{#each item.items as subitem}
|
||||
<Self item={subitem} />
|
||||
{/each}
|
||||
</ul>
|
||||
</details>
|
||||
{:else}
|
||||
<a href={item.url} class="flex gap-2 items-center" use:isActiveLink={{ className: 'menu-active' }}>
|
||||
{#if item.icon}
|
||||
<item.icon size={16} />
|
||||
{/if}
|
||||
{item.title}
|
||||
</a>
|
||||
{/if}
|
||||
</li>
|
||||
57
front/src/lib/navigation/sidebar.svelte
Normal file
57
front/src/lib/navigation/sidebar.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { BrushCleaning, Database, FileSearch, Home, Scale, Search } from "@lucide/svelte";
|
||||
import SidebarMenuItem from "./sidebar-menu-item.svelte";
|
||||
|
||||
interface NavItem {
|
||||
title?: string;
|
||||
url?: string;
|
||||
items?: NavItem[];
|
||||
icon?: any;
|
||||
type?: "link" | "parent" | "divider";
|
||||
}
|
||||
|
||||
const Nav: NavItem[] = [
|
||||
{
|
||||
title: "Home",
|
||||
url: "/",
|
||||
icon: Home,
|
||||
},
|
||||
{
|
||||
title: "Search",
|
||||
url: "/search",
|
||||
icon: Search,
|
||||
},
|
||||
{
|
||||
title: "Data wells",
|
||||
url: "/dataleaks",
|
||||
icon: Database,
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
title: "Parquet files & Rules",
|
||||
url: "/parquet",
|
||||
icon: Scale,
|
||||
},
|
||||
{
|
||||
title: "Leak utils",
|
||||
url: "/leak-utils",
|
||||
icon: BrushCleaning,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="drawer-side z-[101]">
|
||||
<label for="menu-toggle" aria-label="close sidebar" class="drawer-overlay"
|
||||
></label>
|
||||
<ul class="menu bg-base-200 text-base-content min-h-full w-56 p-4 gap-2">
|
||||
{#each Nav as item}
|
||||
{#if item.type === "divider"}
|
||||
<li class="menu-title pt-2">
|
||||
<div class="divider"></div>
|
||||
</li>
|
||||
{:else}
|
||||
<SidebarMenuItem {item} />
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
47
front/src/lib/navigation/topbar.svelte
Normal file
47
front/src/lib/navigation/topbar.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { Github, Menu, Search } from "@lucide/svelte";
|
||||
import Logo from "../components/logo.svelte";
|
||||
import DarkModeToggle from "../components/dark-mode-toggle.svelte";
|
||||
import { cn } from "../utils";
|
||||
import ServerDialog from "../components/server-dialog.svelte";
|
||||
|
||||
let y = $state(0);
|
||||
</script>
|
||||
|
||||
<svelte:window bind:scrollY={y} />
|
||||
|
||||
<nav
|
||||
class={cn(
|
||||
"w-full h-20 flex gap-5 items-center justify-between px-6 fixed transition-colors duration-1000 z-[100]",
|
||||
y === 0 || "bg-base-200",
|
||||
)}
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<label
|
||||
for="menu-toggle"
|
||||
class="btn btn-ghost btn-sm btn-square drawer-button"
|
||||
>
|
||||
<Menu size={16} />
|
||||
</label>
|
||||
<a href="/" class="flex gap-2 items-center">
|
||||
<Logo size={16} />
|
||||
<p>Eleakxir</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 items-center">
|
||||
<a href="/search">
|
||||
<button class="btn btn-sm btn-ghost btn-square">
|
||||
<Search size={16} />
|
||||
</button>
|
||||
</a>
|
||||
<a href="https://github.com/anotherhadi/eleakxir">
|
||||
<button class="btn btn-sm btn-ghost btn-square">
|
||||
<Github size={16} />
|
||||
</button>
|
||||
</a>
|
||||
<DarkModeToggle />
|
||||
|
||||
<ServerDialog class="btn-sm btn-square" />
|
||||
</div>
|
||||
</nav>
|
||||
15
front/src/lib/stores/server.ts
Normal file
15
front/src/lib/stores/server.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
function persistent(key: string, initial: any) {
|
||||
const stored = localStorage.getItem(key);
|
||||
const data = writable(stored ? stored : initial);
|
||||
|
||||
data.subscribe((value) => {
|
||||
localStorage.setItem(key, value);
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export const serverUrl = persistent("serverUrl", "");
|
||||
export const serverPassword = persistent("serverPassword", "");
|
||||
78
front/src/lib/types.ts
Normal file
78
front/src/lib/types.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
type Query = {
|
||||
Text: string;
|
||||
Column: string;
|
||||
ExactMatch: boolean;
|
||||
};
|
||||
|
||||
type LeakResult = {
|
||||
Duration: number;
|
||||
Error: string;
|
||||
Rows: Array<Record<string, string>>;
|
||||
};
|
||||
|
||||
type GithubResult = {
|
||||
Duration: number;
|
||||
Error: string;
|
||||
|
||||
EmailResult: any;
|
||||
UsernameResult: any;
|
||||
};
|
||||
|
||||
type Result = {
|
||||
Id: string;
|
||||
Status: "pending" | "completed";
|
||||
Date: string;
|
||||
Query: Query;
|
||||
LeakResult: LeakResult;
|
||||
GithubResult: GithubResult;
|
||||
};
|
||||
|
||||
type HistoryItem = {
|
||||
Id: string;
|
||||
Status: "pending" | "completed";
|
||||
Date: string;
|
||||
Query: Query;
|
||||
Results: number;
|
||||
};
|
||||
|
||||
type History = HistoryItem[];
|
||||
|
||||
type ServerSettings = {
|
||||
Folders: string[];
|
||||
CacheFolder: string;
|
||||
Limit: number;
|
||||
MinimumQueryLength: number;
|
||||
|
||||
GithubRecon: boolean;
|
||||
GithubTokenLoaded: boolean;
|
||||
GithubDeepMode: boolean;
|
||||
};
|
||||
|
||||
type Server = {
|
||||
Settings: ServerSettings;
|
||||
|
||||
Dataleaks: Dataleak[];
|
||||
|
||||
TotalRows: number;
|
||||
TotalDataleaks: number;
|
||||
TotalSize: number;
|
||||
};
|
||||
|
||||
type Dataleak = {
|
||||
Name: string;
|
||||
Columns: string[];
|
||||
Length: number;
|
||||
Size: number;
|
||||
};
|
||||
|
||||
export type {
|
||||
Query,
|
||||
LeakResult,
|
||||
History,
|
||||
HistoryItem,
|
||||
GithubResult,
|
||||
Result,
|
||||
ServerSettings,
|
||||
Server,
|
||||
Dataleak,
|
||||
};
|
||||
80
front/src/lib/utils.ts
Normal file
80
front/src/lib/utils.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
// take "2025-09-13T21:14:46.13030464+02:00"
|
||||
// return "13/09/2025 21:14"
|
||||
export function formatDate(date: string) {
|
||||
const d = new Date(date);
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const year = d.getFullYear();
|
||||
const hours = String(d.getHours()).padStart(2, "0");
|
||||
const minutes = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
export function convertNanoSeconds(nanoseconds: number): string {
|
||||
const ONE_MS_IN_NS = 1e6;
|
||||
const ONE_S_IN_NS = 1e9;
|
||||
const ONE_MIN_IN_NS = 6e10;
|
||||
|
||||
if (nanoseconds < ONE_MS_IN_NS) {
|
||||
return `${nanoseconds} ns`; // Garde la sortie en ns pour les très petites valeurs
|
||||
} else if (nanoseconds < ONE_S_IN_NS) {
|
||||
const ms = Math.round(nanoseconds / ONE_MS_IN_NS);
|
||||
return `${ms} ms`;
|
||||
} else if (nanoseconds < ONE_MIN_IN_NS) {
|
||||
const s = Math.round(nanoseconds / ONE_S_IN_NS);
|
||||
return `${s} s`;
|
||||
} else {
|
||||
const totalSeconds = Math.round(nanoseconds / ONE_S_IN_NS);
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
return `${minutes}m${seconds}s`;
|
||||
}
|
||||
}
|
||||
|
||||
type FlatObject = { [key: string]: any };
|
||||
|
||||
export function FlattenObject(obj: object): FlatObject {
|
||||
const flattened: FlatObject = {};
|
||||
|
||||
function recurse(currentObj: any, prefix: string = ""): void {
|
||||
for (const key in currentObj) {
|
||||
if (Object.prototype.hasOwnProperty.call(currentObj, key)) {
|
||||
const value = currentObj[key];
|
||||
const newKey = prefix ? `${prefix}.${key}` : key;
|
||||
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value)
|
||||
) {
|
||||
// Si la valeur est un objet, on continue la récursion
|
||||
recurse(value, newKey);
|
||||
} else if (Array.isArray(value)) {
|
||||
// Si la valeur est un tableau, on itère sur ses éléments
|
||||
value.forEach((item, index) => {
|
||||
// On continue la récursion pour les objets dans le tableau
|
||||
if (typeof item === "object" && item !== null) {
|
||||
recurse(item, `${newKey}.${index}`);
|
||||
} else {
|
||||
// On ajoute les valeurs primitives
|
||||
flattened[`${newKey}.${index}`] = item;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Si la valeur est une primitive, on l'ajoute à l'objet aplati
|
||||
flattened[newKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurse(obj);
|
||||
return flattened;
|
||||
}
|
||||
5
front/src/main.ts
Normal file
5
front/src/main.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { mount } from "svelte";
|
||||
import App from "./App.svelte";
|
||||
import "sv-router/generated";
|
||||
|
||||
mount(App, { target: document.querySelector("#app")! });
|
||||
57
front/src/routes/dataleaks/index.svelte
Normal file
57
front/src/routes/dataleaks/index.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import Datawells from "$src/lib/components/index/search/datawells.svelte";
|
||||
import Stats from "$src/lib/components/index/search/stats.svelte";
|
||||
|
||||
import { serverPassword, serverUrl } from "$src/lib/stores/server";
|
||||
import type { Server } from "$src/lib/types";
|
||||
import axios from "axios";
|
||||
import { navigate } from "sv-router/generated";
|
||||
import { onMount } from "svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
let serverInfo = $state<Server | null>(null);
|
||||
|
||||
onMount(() => {
|
||||
if ($serverUrl === "") {
|
||||
toast.error("Please, configure your server first!");
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.get(`${$serverUrl}/`, {
|
||||
headers: {
|
||||
"X-Password": $serverPassword,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
serverInfo = r.data;
|
||||
console.log(serverInfo);
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error(
|
||||
"Failed to fetch server info. Please, change your server configuration!",
|
||||
);
|
||||
console.log(e);
|
||||
navigate("/");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<header class="flex flex-col gap-2 mb-8">
|
||||
<h1 class="h1"><span class="text-2xl align-middle">🗃️</span> Data wells</h1>
|
||||
<p>List of data wells (databases) available on the connected server.</p>
|
||||
</header>
|
||||
{#if serverInfo}
|
||||
<div class="card card-border border-neutral shadow col-span-full mb-5">
|
||||
<Stats {serverInfo} />
|
||||
</div>
|
||||
<Datawells
|
||||
dataleaks={serverInfo.Dataleaks}
|
||||
showColumns={true}
|
||||
perPage={20}
|
||||
/>
|
||||
{:else}
|
||||
<p>Loading...</p>
|
||||
{/if}
|
||||
</main>
|
||||
137
front/src/routes/index.svelte
Normal file
137
front/src/routes/index.svelte
Normal file
@@ -0,0 +1,137 @@
|
||||
<script lang="ts">
|
||||
import AnimatedBeamMultiple from "$src/lib/components/index/AnimatedBeamMultiple.svelte";
|
||||
import Logo from "$src/lib/components/logo.svelte";
|
||||
import ServerDialog from "$src/lib/components/server-dialog.svelte";
|
||||
import { serverUrl } from "$src/lib/stores/server";
|
||||
import { ArrowRight, Github, Search } from "@lucide/svelte";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let open = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
open = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<header
|
||||
class="min-h-[80vh] relative flex justify-center items-center px-6 py-10"
|
||||
>
|
||||
<div
|
||||
class="absolute top-0 left-0 z-[-10] w-full h-full bg-top transition-opacity duration-[2000ms]"
|
||||
class:opacity-0={!open}
|
||||
class:opacity-100={open}
|
||||
style="
|
||||
background-image:
|
||||
linear-gradient(to bottom, rgba(255,255,255,0) 50%, var(--color-base-100) 100%),
|
||||
url('https://lovable.dev/img/background/gradient-optimized.svg');
|
||||
"
|
||||
></div>
|
||||
<div class="mx-auto max-w-3xl flex gap-8 flex-col">
|
||||
<a href="https://github.com/anotherhadi/eleakxir" target="_blank">
|
||||
<span class="badge badge-lg hover:opacity-90"
|
||||
>✨ Check the Github repo <ArrowRight size={16} /></span
|
||||
>
|
||||
</a>
|
||||
<div class="flex gap-6 items-center">
|
||||
<Logo size={46} class="fill-primary" />
|
||||
<h1 class="font-bold text-7xl">Eleakxir</h1>
|
||||
</div>
|
||||
<p>
|
||||
Eleakxir is a self-hosted search engine that lets you connect to your own
|
||||
private and secure server, explore data wells (parquet files) from
|
||||
multiple sources, and visualize results in a clean, modern web interface.
|
||||
</p>
|
||||
<div class="flex gap-6 items-center">
|
||||
<a href="/search">
|
||||
<button class="btn btn-primary">
|
||||
<Search size={16} />
|
||||
Let's search</button
|
||||
>
|
||||
</a>
|
||||
<ServerDialog text="Connect to my server" />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex flex-col gap-24 max-w-7xl m-auto mt-10">
|
||||
<div class="card card-dash bg-base-300">
|
||||
<div class="card-body flex flex-col gap-10 lg:flex-row">
|
||||
<div class="flex gap-5 flex-col">
|
||||
<h2 class="card-title text-3xl">⚙️ How Eleakxir works?</h2>
|
||||
<p>
|
||||
You run an Elixir server that manages parquet files from various
|
||||
leaked data sources and multiple OSINT tools. The web client connects
|
||||
to your server via HTTPS and authenticated headers then you can search
|
||||
across indexed leaks and OSINT tools, browse results interactively and
|
||||
review history and stats
|
||||
<br />
|
||||
<br />
|
||||
And it's open source!
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
{#if $serverUrl === "https://" || $serverUrl === ""}
|
||||
<ServerDialog
|
||||
text="Connect your server"
|
||||
class="grow btn-outline btn btn-accent btn-sm"
|
||||
/>
|
||||
{/if}
|
||||
<a href="https://github.com/anotherhadi/eleakxir">
|
||||
<button class="btn btn-outline btn-sm hover:bg-base-200 grow"
|
||||
><Github size={16} /> Check the Github repo</button
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<AnimatedBeamMultiple />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-center mb-10">🚀 Features</h2>
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 justify-center m-auto gap-5"
|
||||
>
|
||||
{#each [{ title: "🔐 Private by design", content: "connect to your own Eleakxir server with a custom URL + password." }, { title: "🛠 Open source & extensible", content: "hack it, self-host it, extend it." }, { title: "📁 Efficient File Format", content: "Uses the columnar Parquet format for high compression and rapid query performance." }, { title: "🔍 OSINT Tools", content: "Includes Github-recon, GHunt, sherlock and more." }, { title: "📜 Standardized Schema", content: "Includes a detailed guide on how to normalize your data leaks for consistent and effective searching across different breaches." }] as value}
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{value.title}</h2>
|
||||
<p>
|
||||
{value.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-center mb-10">🐢 Speed</h2>
|
||||
<p class="max-w-2xl m-auto">
|
||||
While Eleakxir is designed to be storage-efficient rather than
|
||||
lightning-fast, searches will naturally take longer compared to an indexed
|
||||
engine like Elasticsearch. Indexing systems can provide near-instant
|
||||
results, but at the cost of massive disk usage — often requiring multiple
|
||||
terabytes even for relatively modest datasets. In contrast, Eleakxir
|
||||
trades some speed for compactness: for example, I’m able to store 25
|
||||
billion rows in just over 600 GB on entry-level hardware. A query might
|
||||
take around an hour to complete, but the key point is that it’s actually
|
||||
possible to run such searches at home — something that would be completely
|
||||
out of reach if I had to maintain Elasticsearch’s much larger index
|
||||
footprint.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-center mb-10">🚨 Disclaimer</h2>
|
||||
<p class="max-w-lg m-auto">
|
||||
Eleakxir is provided for educational and research purposes only. You are
|
||||
solely responsible for how you use this software. Accessing, storing, or
|
||||
distributing leaked data may be illegal in your jurisdiction. The authors
|
||||
and contributors do not condone or promote illegal activity. Use
|
||||
responsibly and only with data you are legally permitted to process.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="pb-24"></div>
|
||||
28
front/src/routes/layout.svelte
Normal file
28
front/src/routes/layout.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import Sidebar from "$src/lib/navigation/sidebar.svelte";
|
||||
import Topbar from "$src/lib/navigation/topbar.svelte";
|
||||
import { onMount, type Snippet } from "svelte";
|
||||
import { Toaster } from 'svelte-sonner'
|
||||
import { themeChange } from "theme-change";
|
||||
|
||||
let { children }: { children: Snippet } = $props();
|
||||
|
||||
onMount(() => {
|
||||
themeChange(false);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Toaster
|
||||
toastOptions={{
|
||||
class: '!bg-base-300 !text-base-content !border-base-200',
|
||||
}}
|
||||
/>
|
||||
<div class="drawer min-h-svh">
|
||||
<input id="menu-toggle" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content">
|
||||
<Topbar />
|
||||
<div class="mt-20"></div>
|
||||
{@render children()}
|
||||
</div>
|
||||
<Sidebar />
|
||||
</div>
|
||||
18
front/src/routes/leak-utils/index.svelte
Normal file
18
front/src/routes/leak-utils/index.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import axios from "axios";
|
||||
import { marked } from "marked";
|
||||
import { onMount } from "svelte";
|
||||
const url = "https://raw.githubusercontent.com/anotherhadi/eleakxir-temp/refs/heads/main/leak-utils/README.md"
|
||||
|
||||
let text = $state<string>("");
|
||||
|
||||
onMount(() => {
|
||||
axios.get(url).then((r) => {
|
||||
text = r.data;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="prose max-w-7xl pt-16 pb-28">
|
||||
{@html marked(text)}
|
||||
</main>
|
||||
18
front/src/routes/parquet/index.svelte
Normal file
18
front/src/routes/parquet/index.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import axios from "axios";
|
||||
import { marked } from "marked";
|
||||
import { onMount } from "svelte";
|
||||
const url = "https://raw.githubusercontent.com/anotherhadi/eleakxir-temp/refs/heads/main/leak-utils/DATALEAKS-NORMALIZATION.md"
|
||||
|
||||
let text = $state<string>("");
|
||||
|
||||
onMount(() => {
|
||||
axios.get(url).then((r) => {
|
||||
text = r.data;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="prose max-w-7xl pt-16 pb-28">
|
||||
{@html marked(text)}
|
||||
</main>
|
||||
226
front/src/routes/search/[id]/index.svelte
Normal file
226
front/src/routes/search/[id]/index.svelte
Normal file
@@ -0,0 +1,226 @@
|
||||
<script lang="ts">
|
||||
import type { Result } from "$src/lib/types";
|
||||
import axios from "axios";
|
||||
import { navigate, route } from "sv-router/generated";
|
||||
import { serverPassword, serverUrl } from "$src/lib/stores/server";
|
||||
import Searchbar from "$src/lib/components/index/search/searchbar.svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { onMount } from "svelte";
|
||||
import Stats from "$src/lib/components/index/search/id/stats.svelte";
|
||||
import Rows from "$src/lib/components/index/search/id/rows.svelte";
|
||||
import {
|
||||
ChevronDown,
|
||||
CircleAlert,
|
||||
CircleCheck,
|
||||
CircleMinus,
|
||||
CircleX,
|
||||
Database,
|
||||
Github,
|
||||
} from "@lucide/svelte";
|
||||
import { convertNanoSeconds } from "$src/lib/utils";
|
||||
import GithubResult from "$src/lib/components/index/search/id/githubResult.svelte";
|
||||
|
||||
route.getParams("/search/:id");
|
||||
|
||||
let { id } = route.params;
|
||||
|
||||
let result = $state<Result | null>(null);
|
||||
|
||||
function loadData() {
|
||||
if (id === undefined) {
|
||||
return;
|
||||
}
|
||||
if (id === "") {
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.get(`${$serverUrl}/search/${id}`, {
|
||||
headers: {
|
||||
"X-Password": $serverPassword,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
result = r.data;
|
||||
console.log(r.data);
|
||||
if (result && result.Status !== "pending") {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error("Failed to fetch search result!");
|
||||
clearInterval(intervalId);
|
||||
navigate("/search");
|
||||
});
|
||||
}
|
||||
|
||||
let intervalId: ReturnType<typeof setInterval>;
|
||||
let elapsedTime = 0;
|
||||
let pollingInterval = 10000; // Start with a 10-second interval
|
||||
|
||||
onMount(() => {
|
||||
if ($serverUrl === "") {
|
||||
toast.error("Please, configure your server first!");
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
|
||||
loadData();
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
elapsedTime += pollingInterval;
|
||||
|
||||
// Check for status change inside the interval
|
||||
if (result && result.Status !== "pending") {
|
||||
clearInterval(intervalId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Change polling frequency based on elapsed time
|
||||
if (elapsedTime >= 120000 && pollingInterval !== 10000) {
|
||||
clearInterval(intervalId);
|
||||
pollingInterval = 15000;
|
||||
intervalId = setInterval(loadData, pollingInterval);
|
||||
return;
|
||||
} else if (elapsedTime >= 600000 && pollingInterval !== 30000) {
|
||||
clearInterval(intervalId);
|
||||
pollingInterval = 30000;
|
||||
intervalId = setInterval(loadData, pollingInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
loadData();
|
||||
}, pollingInterval);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
{#if result}
|
||||
<header class="flex gap-5 flex-col">
|
||||
<a href="/search">
|
||||
<h1 class="h1"><span class="text-2xl align-middle">🔍</span> Search</h1>
|
||||
</a>
|
||||
|
||||
<Searchbar
|
||||
initialQuery={result.Query.Text}
|
||||
initialFilter={result.Query.Column}
|
||||
initialExactMatch={result.Query.ExactMatch}
|
||||
/>
|
||||
</header>
|
||||
|
||||
<div class="my-10"></div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-5 [&>div]:border-neutral">
|
||||
<div class="card card-border shadow col-span-full">
|
||||
<Stats {result} />
|
||||
</div>
|
||||
|
||||
<div class="collapse collapse-arrow bg-base-100 border">
|
||||
<input type="radio" name="my-accordion-2" checked={true} />
|
||||
<div
|
||||
class="collapse-title font-semibold text-xl flex justify-between items-center"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Database size={18} class="text-base-content/60" />
|
||||
Data wells lookup
|
||||
</div>
|
||||
{#if result.LeakResult.Error !== ""}
|
||||
<CircleX size={16} class="text-error" />
|
||||
{:else if result.LeakResult.Duration === 0}
|
||||
<span class="loading loading-dots loading-xs"></span>
|
||||
{:else if result.LeakResult.Rows.length > 0}
|
||||
<CircleCheck size={16} class="text-success" />
|
||||
{:else}
|
||||
<CircleMinus size={16} class="text-base-content/60" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
{#if result.LeakResult.Error !== ""}
|
||||
<div role="alert" class="alert alert-soft alert-error">
|
||||
<CircleAlert size={20} />
|
||||
<span>Error! {result.LeakResult.Error}</span>
|
||||
</div>
|
||||
{:else if result.LeakResult.Duration === 0}
|
||||
<ul class="list rounded-box">
|
||||
{#each Array(5) as _}
|
||||
<div class="list-row text-left">
|
||||
<div>
|
||||
<div
|
||||
class="skeleton size-10 rounded-box items-center justify-center flex"
|
||||
></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="skeleton h-5 mb-1 w-52"></div>
|
||||
<div
|
||||
class="text-xs skeleton h-4 w-34 uppercase font-semibold opacity-60"
|
||||
></div>
|
||||
</div>
|
||||
<div class="btn btn-square btn-ghost">
|
||||
<ChevronDown size={12} />
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<p class="text-base-content/60">
|
||||
{result.LeakResult.Rows.length} results in {convertNanoSeconds(
|
||||
result.LeakResult.Duration,
|
||||
)}
|
||||
</p>
|
||||
<Rows {result} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse collapse-arrow bg-base-100 border">
|
||||
<input type="radio" name="my-accordion-2" />
|
||||
<div
|
||||
class="collapse-title font-semibold text-xl flex justify-between items-center"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Github size={18} class="text-base-content/60" />
|
||||
Github Recon
|
||||
</div>
|
||||
{#if result.GithubResult.Error !== ""}
|
||||
<CircleX size={16} class="text-error" />
|
||||
{:else if result.GithubResult.Duration === 0}
|
||||
<span class="loading loading-dots loading-xs"></span>
|
||||
{:else if !result.GithubResult.EmailResult?.Commits && !result.GithubResult.EmailResult?.Spoofing && !result.GithubResult.UsernameResult?.User}
|
||||
<CircleMinus size={16} class="text-base-content/60" />
|
||||
{:else if result.GithubResult.UsernameResult || result.GithubResult.EmailResult}
|
||||
<CircleCheck size={16} class="text-success" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
{#if result.GithubResult.Error !== ""}
|
||||
<div role="alert" class="alert alert-soft alert-error">
|
||||
<CircleAlert size={20} />
|
||||
<span>Error! {result.GithubResult.Error}</span>
|
||||
</div>
|
||||
{:else if result.GithubResult.Duration === 0}
|
||||
<div role="alert" class="alert alert-soft">
|
||||
<span class="loading loading-dots loading-sm"></span>
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
{:else if !result.GithubResult.EmailResult?.Commits && !result.GithubResult.EmailResult?.Spoofing && !result.GithubResult.UsernameResult?.User}
|
||||
<div role="alert" class="alert alert-soft">
|
||||
<CircleMinus size={20} />
|
||||
<span>No result</span>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-base-content/60 mb-4">
|
||||
Found a result in {convertNanoSeconds(
|
||||
result.GithubResult.Duration,
|
||||
)}
|
||||
</p>
|
||||
<GithubResult githubResult={result.GithubResult} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-10"></div>
|
||||
</main>
|
||||
94
front/src/routes/search/index.svelte
Normal file
94
front/src/routes/search/index.svelte
Normal file
@@ -0,0 +1,94 @@
|
||||
<script lang="ts">
|
||||
import Datawells from "$src/lib/components/index/search/datawells.svelte";
|
||||
import History from "$src/lib/components/index/search/history.svelte";
|
||||
import HowToSearch from "$src/lib/components/index/search/howToSearch.svelte";
|
||||
import Searchbar from "$src/lib/components/index/search/searchbar.svelte";
|
||||
import Services from "$src/lib/components/index/search/services.svelte";
|
||||
import Stats from "$src/lib/components/index/search/stats.svelte";
|
||||
import { serverPassword, serverUrl } from "$src/lib/stores/server";
|
||||
import type { Server, History as HistoryT } from "$src/lib/types";
|
||||
import axios from "axios";
|
||||
import { navigate } from "sv-router/generated";
|
||||
import { onMount } from "svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
let serverInfo = $state<Server | null>(null);
|
||||
let history = $state<HistoryT>([]);
|
||||
|
||||
onMount(() => {
|
||||
if ($serverUrl === "") {
|
||||
toast.error("Please, configure your server first!");
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.get(`${$serverUrl}/`, {
|
||||
headers: {
|
||||
"X-Password": $serverPassword,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
serverInfo = r.data;
|
||||
console.log(serverInfo);
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error(
|
||||
"Failed to fetch server info. Please, change your server configuration!",
|
||||
);
|
||||
navigate("/");
|
||||
});
|
||||
axios
|
||||
.get(`${$serverUrl}/history`, {
|
||||
headers: {
|
||||
"X-Password": $serverPassword,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
history = r.data.History;
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error("Failed to fetch history");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<header class="flex gap-5 flex-col">
|
||||
<h1 class="h1"><span class="text-2xl align-middle">🔍</span> Search</h1>
|
||||
<Searchbar />
|
||||
</header>
|
||||
|
||||
<div class="my-10"></div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div class="card card-border border-neutral shadow col-span-full">
|
||||
<Stats {serverInfo} />
|
||||
</div>
|
||||
<div class="card card-border border-neutral shadow card-body">
|
||||
<h2 class="h2">History</h2>
|
||||
<History {history} />
|
||||
</div>
|
||||
<div class="card card-border border-neutral shadow card-body">
|
||||
<h2 class="h2">Active services</h2>
|
||||
<div class="overflow-x-auto">
|
||||
{#if !serverInfo}
|
||||
<p>Loading...</p>
|
||||
{:else}
|
||||
<Services {serverInfo} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-border border-neutral shadow card-body">
|
||||
<h2 class="h2">Last data wells added</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<Datawells dataleaks={serverInfo?.Dataleaks || []} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-border border-neutral shadow card-body">
|
||||
<h2 class="h2">How to search</h2>
|
||||
<HowToSearch />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-10"></div>
|
||||
</main>
|
||||
2
front/src/vite-env.d.ts
vendored
Normal file
2
front/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
6
front/svelte.config.js
Normal file
6
front/svelte.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('@sveltejs/vite-plugin-svelte').SvelteConfig} */
|
||||
export default {
|
||||
compilerOptions: {
|
||||
runes: true,
|
||||
},
|
||||
};
|
||||
21
front/tsconfig.json
Normal file
21
front/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": ["./.router/tsconfig.json"],
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "preserve",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$src": ["./src"],
|
||||
"$src/*": ["./src/*"],
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"],
|
||||
"sv-router/generated": [".router/router.ts"],
|
||||
}
|
||||
}
|
||||
}
|
||||
15
front/vite.config.ts
Normal file
15
front/vite.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { router } from "sv-router/vite-plugin";
|
||||
import { defineConfig } from "vite";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), svelte({}), router()],
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve("./src/lib"),
|
||||
$src: path.resolve("./src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user