This commit is contained in:
Hadi
2025-09-24 17:20:03 +02:00
commit b9fbed9a54
83 changed files with 6241 additions and 0 deletions

26
front/.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

6
front/src/App.svelte Normal file
View 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
View 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;
}

View 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}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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}

View 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}

View 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}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
import { mount } from "svelte";
import App from "./App.svelte";
import "sv-router/generated";
mount(App, { target: document.querySelector("#app")! });

View 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>

View 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, Im 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 its actually
possible to run such searches at home — something that would be completely
out of reach if I had to maintain Elasticsearchs 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>

View 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>

View 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>

View 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>

View 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>

View 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
View File

@@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

6
front/svelte.config.js Normal file
View File

@@ -0,0 +1,6 @@
/** @type {import('@sveltejs/vite-plugin-svelte').SvelteConfig} */
export default {
compilerOptions: {
runes: true,
},
};

21
front/tsconfig.json Normal file
View 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
View 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"),
},
},
});