diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..17acbbb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false +insert_final_newline = false diff --git a/.gitignore b/.gitignore index c9641c2..72e2137 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ !db.json !private.json !public.json +!package.json +!tsconfig*.json # Byte-compiled / optimized / DLL files @@ -22,6 +24,7 @@ downloads/ eggs/ .eggs/ lib/ +!/admin/frontend/src/lib/ lib64/ parts/ sdist/ diff --git a/admin/frontend/.gitignore b/admin/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/admin/frontend/.gitignore @@ -0,0 +1,24 @@ +# 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? diff --git a/admin/frontend/Dockerfile b/admin/frontend/Dockerfile new file mode 100644 index 0000000..b4d77d6 --- /dev/null +++ b/admin/frontend/Dockerfile @@ -0,0 +1,11 @@ +FROM node:22-slim +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +WORKDIR /app +COPY package.json pnpm-lock.yaml /app/ + +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile + +CMD [ "pnpm", "dev", "--host", "0.0.0.0" ] \ No newline at end of file diff --git a/admin/frontend/index.html b/admin/frontend/index.html new file mode 100644 index 0000000..eaa1731 --- /dev/null +++ b/admin/frontend/index.html @@ -0,0 +1,11 @@ + + + + + + + +
+ + + diff --git a/admin/frontend/package.json b/admin/frontend/package.json new file mode 100644 index 0000000..3d4817c --- /dev/null +++ b/admin/frontend/package.json @@ -0,0 +1,25 @@ +{ + "private": true, + "type": "module", + "contributors": [ + "Simon Lagerlöf " + ], + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "lucide-vue-next": "^0.544.0", + "valibot": "^1.1.0", + "vue": "^3.5.18", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.7.0", + "typescript": "~5.8.3", + "vite": "^7.1.2", + "vue-tsc": "^3.0.5" + } +} diff --git a/admin/frontend/pnpm-lock.yaml b/admin/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..5a3de56 --- /dev/null +++ b/admin/frontend/pnpm-lock.yaml @@ -0,0 +1,959 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + lucide-vue-next: + specifier: ^0.544.0 + version: 0.544.0(vue@3.5.21(typescript@5.8.3)) + valibot: + specifier: ^1.1.0 + version: 1.1.0(typescript@5.8.3) + vue: + specifier: ^3.5.18 + version: 3.5.21(typescript@5.8.3) + vue-router: + specifier: ^4.5.1 + version: 4.5.1(vue@3.5.21(typescript@5.8.3)) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.1(vite@7.1.4)(vue@3.5.21(typescript@5.8.3)) + '@vue/tsconfig': + specifier: ^0.7.0 + version: 0.7.0(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3)) + typescript: + specifier: ~5.8.3 + version: 5.8.3 + vite: + specifier: ^7.1.2 + version: 7.1.4 + vue-tsc: + specifier: ^3.0.5 + version: 3.0.6(typescript@5.8.3) + +packages: + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + + '@rollup/rollup-android-arm-eabi@4.50.0': + resolution: {integrity: sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.50.0': + resolution: {integrity: sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.50.0': + resolution: {integrity: sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.50.0': + resolution: {integrity: sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.50.0': + resolution: {integrity: sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.50.0': + resolution: {integrity: sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.50.0': + resolution: {integrity: sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.50.0': + resolution: {integrity: sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.50.0': + resolution: {integrity: sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.50.0': + resolution: {integrity: sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.50.0': + resolution: {integrity: sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.50.0': + resolution: {integrity: sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.50.0': + resolution: {integrity: sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.50.0': + resolution: {integrity: sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.50.0': + resolution: {integrity: sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.50.0': + resolution: {integrity: sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.50.0': + resolution: {integrity: sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.50.0': + resolution: {integrity: sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.50.0': + resolution: {integrity: sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.50.0': + resolution: {integrity: sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.50.0': + resolution: {integrity: sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@vitejs/plugin-vue@6.0.1': + resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + + '@vue/compiler-core@3.5.21': + resolution: {integrity: sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==} + + '@vue/compiler-dom@3.5.21': + resolution: {integrity: sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==} + + '@vue/compiler-sfc@3.5.21': + resolution: {integrity: sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==} + + '@vue/compiler-ssr@3.5.21': + resolution: {integrity: sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@3.0.6': + resolution: {integrity: sha512-e2RRzYWm+qGm8apUHW1wA5RQxzNhkqbbKdbKhiDUcmMrNAZGyM8aTiL3UrTqkaFI5s7wJRGGrp4u3jgusuBp2A==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.21': + resolution: {integrity: sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==} + + '@vue/runtime-core@3.5.21': + resolution: {integrity: sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==} + + '@vue/runtime-dom@3.5.21': + resolution: {integrity: sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==} + + '@vue/server-renderer@3.5.21': + resolution: {integrity: sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==} + peerDependencies: + vue: 3.5.21 + + '@vue/shared@3.5.21': + resolution: {integrity: sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==} + + '@vue/tsconfig@0.7.0': + resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + alien-signals@2.0.7: + resolution: {integrity: sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + lucide-vue-next@0.544.0: + resolution: {integrity: sha512-mDp/AdGOPIDkpFHnFiTWgQgCST9aBXHVaiobZfOMIvv7nrOukzF/TP+7KoOwrngdWRaH9TMiepMBIX1vsgKJ3g==} + peerDependencies: + vue: '>=3.0.1' + + magic-string@0.30.18: + resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.50.0: + resolution: {integrity: sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + valibot@1.1.0: + resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + + vite@7.1.4: + resolution: {integrity: sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-router@4.5.1: + resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==} + peerDependencies: + vue: ^3.2.0 + + vue-tsc@3.0.6: + resolution: {integrity: sha512-Tbs8Whd43R2e2nxez4WXPvvdjGbW24rOSgRhLOHXzWiT4pcP4G7KeWh0YCn18rF4bVwv7tggLLZ6MJnO6jXPBg==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.21: + resolution: {integrity: sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + +snapshots: + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-x64@0.25.9': + optional: true + + '@esbuild/linux-arm64@0.25.9': + optional: true + + '@esbuild/linux-arm@0.25.9': + optional: true + + '@esbuild/linux-ia32@0.25.9': + optional: true + + '@esbuild/linux-loong64@0.25.9': + optional: true + + '@esbuild/linux-mips64el@0.25.9': + optional: true + + '@esbuild/linux-ppc64@0.25.9': + optional: true + + '@esbuild/linux-riscv64@0.25.9': + optional: true + + '@esbuild/linux-s390x@0.25.9': + optional: true + + '@esbuild/linux-x64@0.25.9': + optional: true + + '@esbuild/netbsd-arm64@0.25.9': + optional: true + + '@esbuild/netbsd-x64@0.25.9': + optional: true + + '@esbuild/openbsd-arm64@0.25.9': + optional: true + + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + + '@esbuild/sunos-x64@0.25.9': + optional: true + + '@esbuild/win32-arm64@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rolldown/pluginutils@1.0.0-beta.29': {} + + '@rollup/rollup-android-arm-eabi@4.50.0': + optional: true + + '@rollup/rollup-android-arm64@4.50.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.50.0': + optional: true + + '@rollup/rollup-darwin-x64@4.50.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.50.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.50.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.50.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.50.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.50.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.50.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.50.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.50.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.50.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.50.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.50.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.50.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.50.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.50.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.50.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.50.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.50.0': + optional: true + + '@types/estree@1.0.8': {} + + '@vitejs/plugin-vue@6.0.1(vite@7.1.4)(vue@3.5.21(typescript@5.8.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.1.4 + vue: 3.5.21(typescript@5.8.3) + + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.21': + dependencies: + '@babel/parser': 7.28.3 + '@vue/shared': 3.5.21 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.21': + dependencies: + '@vue/compiler-core': 3.5.21 + '@vue/shared': 3.5.21 + + '@vue/compiler-sfc@3.5.21': + dependencies: + '@babel/parser': 7.28.3 + '@vue/compiler-core': 3.5.21 + '@vue/compiler-dom': 3.5.21 + '@vue/compiler-ssr': 3.5.21 + '@vue/shared': 3.5.21 + estree-walker: 2.0.2 + magic-string: 0.30.18 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.21': + dependencies: + '@vue/compiler-dom': 3.5.21 + '@vue/shared': 3.5.21 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@3.0.6(typescript@5.8.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.21 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.21 + alien-signals: 2.0.7 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + optionalDependencies: + typescript: 5.8.3 + + '@vue/reactivity@3.5.21': + dependencies: + '@vue/shared': 3.5.21 + + '@vue/runtime-core@3.5.21': + dependencies: + '@vue/reactivity': 3.5.21 + '@vue/shared': 3.5.21 + + '@vue/runtime-dom@3.5.21': + dependencies: + '@vue/reactivity': 3.5.21 + '@vue/runtime-core': 3.5.21 + '@vue/shared': 3.5.21 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.21(vue@3.5.21(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.21 + '@vue/shared': 3.5.21 + vue: 3.5.21(typescript@5.8.3) + + '@vue/shared@3.5.21': {} + + '@vue/tsconfig@0.7.0(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3))': + optionalDependencies: + typescript: 5.8.3 + vue: 3.5.21(typescript@5.8.3) + + alien-signals@2.0.7: {} + + csstype@3.1.3: {} + + de-indent@1.0.2: {} + + entities@4.5.0: {} + + 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 + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fsevents@2.3.3: + optional: true + + he@1.2.0: {} + + lucide-vue-next@0.544.0(vue@3.5.21(typescript@5.8.3)): + dependencies: + vue: 3.5.21(typescript@5.8.3) + + magic-string@0.30.18: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + 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.3 + + source-map-js@1.2.1: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + typescript@5.8.3: {} + + valibot@1.1.0(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + + vite@7.1.4: + dependencies: + esbuild: 0.25.9 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.50.0 + tinyglobby: 0.2.14 + optionalDependencies: + fsevents: 2.3.3 + + vscode-uri@3.1.0: {} + + vue-router@4.5.1(vue@3.5.21(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.21(typescript@5.8.3) + + vue-tsc@3.0.6(typescript@5.8.3): + dependencies: + '@volar/typescript': 2.4.23 + '@vue/language-core': 3.0.6(typescript@5.8.3) + typescript: 5.8.3 + + vue@3.5.21(typescript@5.8.3): + dependencies: + '@vue/compiler-dom': 3.5.21 + '@vue/compiler-sfc': 3.5.21 + '@vue/runtime-dom': 3.5.21 + '@vue/server-renderer': 3.5.21(vue@3.5.21(typescript@5.8.3)) + '@vue/shared': 3.5.21 + optionalDependencies: + typescript: 5.8.3 diff --git a/admin/frontend/src/App.vue b/admin/frontend/src/App.vue new file mode 100644 index 0000000..7a86ddf --- /dev/null +++ b/admin/frontend/src/App.vue @@ -0,0 +1,21 @@ + + + diff --git a/admin/frontend/src/components/TrustMarkTypesList.vue b/admin/frontend/src/components/TrustMarkTypesList.vue new file mode 100644 index 0000000..cdeff2a --- /dev/null +++ b/admin/frontend/src/components/TrustMarkTypesList.vue @@ -0,0 +1,38 @@ + + + diff --git a/admin/frontend/src/components/base/Heading.vue b/admin/frontend/src/components/base/Heading.vue new file mode 100644 index 0000000..5657359 --- /dev/null +++ b/admin/frontend/src/components/base/Heading.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/admin/frontend/src/components/site/Layout.vue b/admin/frontend/src/components/site/Layout.vue new file mode 100644 index 0000000..8d708b6 --- /dev/null +++ b/admin/frontend/src/components/site/Layout.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/admin/frontend/src/components/site/Sidebar.vue b/admin/frontend/src/components/site/Sidebar.vue new file mode 100644 index 0000000..8f3375e --- /dev/null +++ b/admin/frontend/src/components/site/Sidebar.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/admin/frontend/src/lib/admin-sdk/errors.ts b/admin/frontend/src/lib/admin-sdk/errors.ts new file mode 100644 index 0000000..119fb33 --- /dev/null +++ b/admin/frontend/src/lib/admin-sdk/errors.ts @@ -0,0 +1,64 @@ +import type { BaseIssue, BaseSchema, SafeParseResult } from 'valibot'; + +export class AdminSDKError extends Error {}; + + +type TSchema = BaseSchema>; + +type Issues = SafeParseResult['issues'] + +type ValidationErrorProps = { + message?: string; + issues?: Issues; +} + +export class ValidationError extends AdminSDKError { + readonly issues?: Issues; + readonly message: string; + + constructor({ message, issues }: ValidationErrorProps){ + super(`Validation error: ${message}`); + this.issues = issues; + this.message = message || 'An unexpected error occurred.'; + } +}; + +type FetchErrorProps = { + status?: number; + message?: string; +}; +export type FetchErrorType = 'client_error' | 'auth_error' | 'server_error' | 'unknown_error'; +export class FetchError extends AdminSDKError { + readonly status?: number; + readonly type: FetchErrorType; + readonly message: string; + + constructor({ status, message }: FetchErrorProps) { + const errors: Record = { + 400: { type: 'client_error', message: 'Request is invalid.' }, + 405: { type: 'client_error', message: 'Method not allowed.' }, + 401: { type: 'auth_error', message: 'Authorization failed.' }, + 403: { type: 'auth_error', message: 'Access is forbidden.' }, + 500: { type: 'server_error', message: 'Internal server error.' }, + 501: { type: 'server_error', message: 'Requested functionality is not supported.' }, + 502: { type: 'server_error', message: 'Invalid response received from upstream server.' }, + 503: { type: 'server_error', message: 'Service unavailable.' }, + 504: { type: 'server_error', message: 'Gateway timeout: server did not respond in time.' }, + }; + + const error = status && errors[status] || { + type: 'unknown_error', + message: 'An unexpected error occurred.' + }; + + if (message) { + error.message = message; + } + + + super(`Fetch error: ${error.message}`); + this.status = status; + this.type = error.type; + this.message = error.message; + } +} diff --git a/admin/frontend/src/lib/admin-sdk/index.ts b/admin/frontend/src/lib/admin-sdk/index.ts new file mode 100644 index 0000000..5d22397 --- /dev/null +++ b/admin/frontend/src/lib/admin-sdk/index.ts @@ -0,0 +1,3 @@ +export * from './sdk'; +export * from './resources'; +export * from './errors'; \ No newline at end of file diff --git a/admin/frontend/src/lib/admin-sdk/resources.ts b/admin/frontend/src/lib/admin-sdk/resources.ts new file mode 100644 index 0000000..6d474b7 --- /dev/null +++ b/admin/frontend/src/lib/admin-sdk/resources.ts @@ -0,0 +1,108 @@ +import { + array, + boolean, + number, + object, + record, + string, + unknown, + type InferOutput +} from 'valibot'; + +export type HttpMethod = 'GET'|'POST'|'DELETE'|'PATCH'|'PUT'; + +export type TrustMarkTypeCreateOptions = InferOutput; +export const TrustMarkTypeCreateOptionsSchema = object({ + tmtype: string(), + autorenew: boolean(), + valid_for: number(), + renewal_time: number(), + active: boolean(), +}) + +export type TrustMarkTypeUpdateOptions = InferOutput; +export const TrustMarkTypeUpdateOptionsSchema = object({ + autorenew: boolean(), + valid_for: number(), + renewal_time: number(), + active: boolean(), +}); + +export type TrustMarkType = InferOutput; +export const TrustMarkTypeSchema = object({ + id: number(), + tmtype: string(), + autorenew: boolean(), + valid_for: number(), + renewal_time: number(), + active: boolean(), +}); + +export type TrustMarkTypes = InferOutput; +export const TrustMarkTypesSchema = object({ + items: array(TrustMarkTypeSchema), + count: number(), +}); + +export type TrustMarkCreateOptions = InferOutput; +export const TrustMarkCreateOptionsSchema = object({ + tmt: number(), + domain: string(), + autorenew: boolean(), + valid_for: number(), + renewal_time: number(), + active: boolean(), +}); + +export type TrustMarkUpdateOptions = InferOutput; +export const TrustMarkUpdateOptionsSchema = object({ + autorenew: boolean(), + active: boolean(), +}) + +export type TrustMark = InferOutput; +export const TrustMarkSchema = object({ + id: number(), + domain: string(), + expire_at: string(), + autorenew: boolean(), + valid_for: number(), + renewal_time: number(), + active: boolean(), + mark: string(), +}); + +export type TrustMarks = InferOutput; +export const TrustMarksSchema = object({ + items: array(TrustMarkSchema), + count: number(), +}); + +export type SubordinateCreateOptions = InferOutput; +export const SubordinateCreateOptionsSchema = object({ + entityid: string(), + metadata: record(string(), unknown()), + jwks: record(string(), unknown()), + required_trustmarks: string(), + valid_for: number(), + autorenew: boolean(), + active: boolean(), +}); + +export type Subordinate = InferOutput; +export const SubordinateSchema = object({ + id: number(), + entityid: string(), + metadata: record(string(), unknown()), + jwks: record(string(), unknown()), + required_trustmarks: string(), + valid_for: number(), + autorenew: boolean(), + active: boolean(), +}) + +export type Subordinates = InferOutput; +export const SubordinatesSchema = object({ + items: array(SubordinateSchema), + count: number(), +}); diff --git a/admin/frontend/src/lib/admin-sdk/sdk.ts b/admin/frontend/src/lib/admin-sdk/sdk.ts new file mode 100644 index 0000000..ef791d2 --- /dev/null +++ b/admin/frontend/src/lib/admin-sdk/sdk.ts @@ -0,0 +1,347 @@ +import { safeParse } from 'valibot'; +import { FetchError, ValidationError } from './errors'; +import { + SubordinateCreateOptionsSchema, + SubordinateSchema, + SubordinatesSchema, + TrustMarkCreateOptionsSchema, + TrustMarkSchema, + TrustMarksSchema, + TrustMarkTypeCreateOptionsSchema, + TrustMarkTypeSchema, + TrustMarkTypesSchema, + TrustMarkTypeUpdateOptionsSchema, + TrustMarkUpdateOptionsSchema, + type HttpMethod, + type Subordinate, + type SubordinateCreateOptions, + type Subordinates, + type TrustMark, + type TrustMarkCreateOptions, + type TrustMarks, + type TrustMarkType, + type TrustMarkTypeCreateOptions, + type TrustMarkTypes, + type TrustMarkTypeUpdateOptions, + type TrustMarkUpdateOptions +} from './resources'; + +type Config = { + apiUrl: URL; +}; + +export class AdminSDK { + readonly #apiUrl: URL; + + constructor(config: Config) { + const { apiUrl } = config; + this.#apiUrl = apiUrl; + } + + /** + * Create Trust Mark Type. + */ + async createTrustMarkType(options: TrustMarkTypeCreateOptions): Promise { + const body = safeParse(TrustMarkTypeCreateOptionsSchema, options); + if (!body.success) { + throw new ValidationError({ + message: 'Failed to validate trustmark type creation options', + issues: body.issues, + }); + } + + const res = await this.#fetch('POST', '/trustmarktypes', { + body: JSON.stringify(body.output), + }); + + const data = safeParse(TrustMarkTypeSchema, res); + if (!data.success) { + throw new ValidationError({ + message: 'Invalid response when creating trustmark type', + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Lists all existing TrustMarkType(s). + */ + async listTrustMarkTypes(filters?: { limit?: number; offset?: number; }): Promise { + const res = await this.#fetch('GET', '/trustmarktypes', { + filters, + }); + + const data = safeParse(TrustMarkTypesSchema, res); + if (!data.success) { + throw new ValidationError({ + message: 'Invalid response when listing trustmark types', + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Gets a TrustMarkType by ID. + */ + async getTrustMarkTypeById(id: number): Promise { + const res = await this.#fetch('GET', `/trustmarktypes/${id}`); + + const data = safeParse(TrustMarkTypeSchema, res); + if (!data.success) { + throw new ValidationError({ + message: `Invalid reponse when getting trustmark type by id ${id}`, + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Update a TrustMarkType by ID. + */ + async updateTrustMarkType(id: number, options: TrustMarkTypeUpdateOptions): Promise { + const body = safeParse(TrustMarkTypeUpdateOptionsSchema, options); + if (!body.success) { + throw new ValidationError({ + message: 'Failed to validate trustmark type update options', + issues: body.issues, + }); + } + + const res = await this.#fetch('PUT', `/trustmarktypes/${id}`, { + body: JSON.stringify(body.output) + }); + + const data = safeParse(TrustMarkTypeSchema, res); + if (!data.success) { + throw new ValidationError({ + message: `Invalid response when updating trustmark with id ${id}`, + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Create Trust Mark. + */ + async createTrustMark(options: TrustMarkCreateOptions): Promise { + const body = safeParse(TrustMarkCreateOptionsSchema, options); + if (!body.success) { + throw new ValidationError({ + message: 'Failed to validate trustmark creation options', + issues: body.issues, + }); + } + + const res = await this.#fetch('POST', '/trustmarks', { + body: JSON.stringify(body.output), + }); + + const data = safeParse(TrustMarkSchema, res); + if (!data.success) { + throw new ValidationError({ + message: 'Invalid response when creating trustmark', + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Lists all existing TrustMarks. + */ + async listTrustMarks(filters?: { limit?: number; offset?: number; }): Promise { + const res = await this.#fetch('GET', '/trustmarks', { + filters, + }); + + const data = safeParse(TrustMarksSchema, res); + if (!data.success) { + throw new ValidationError({ + message: 'Invalid response when listing trustmarks', + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Returns a list of existing TrustMarks for a given domain. + */ + async listTrustMarksByDomain(domain: string, filters?: { limit?: number; offset?: number; }): Promise { + const res = await this.#fetch('POST', '/trustmarks/list', { + filters, + body: JSON.stringify({ domain }) + }); + + const data = safeParse(TrustMarksSchema, res); + if (!data.success) { + throw new ValidationError({ + message: `Invalid reponse when listing trustmarks by domain ${domain}`, + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Renews a TrustMark. + */ + async renewTrustMark(id: number): Promise { + const res = await this.#fetch('POST', `/trustmarks/${id}/renew`); + + const data = safeParse(TrustMarkSchema, res); + if (!data.success) { + throw new ValidationError({ + message: `Invalid response when renewing trustmark with id ${id}`, + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Update a TrustMark. + */ + async updateTrustMark(id: number, options: TrustMarkUpdateOptions): Promise { + const body = safeParse(TrustMarkUpdateOptionsSchema, options); + if (!body.success) { + throw new ValidationError({ + message: 'Failed to validate trustmark update options', + issues: body.issues, + }); + } + + const res = await this.#fetch('POST', `/trustmarks/${id}`); + + const data = safeParse(TrustMarkSchema, res); + if (!data.success) { + throw new ValidationError({ + message: `Invalid response when updating trust mark with id ${id}`, + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Create Subordinate. + */ + async createSubordinate(options: SubordinateCreateOptions): Promise { + const body = safeParse(SubordinateCreateOptionsSchema, options); + if (!body.success) { + throw new ValidationError({ + message: 'Failed to validate subordinate creation options', + issues: body.issues, + }); + } + + const res = await this.#fetch('POST', '/subordinates', { + body: JSON.stringify(body.output), + }); + + const data = safeParse(SubordinateSchema, res); + if (!data.success) { + throw new ValidationError({ + message: 'Invalid response when creating subordinate', + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Lists all existing subordinates. + */ + async listSubordinates(filters?: { limit?: number; offset?: number; }): Promise { + const res = await this.#fetch('GET', '/subordinates', { + filters, + }); + + const data = safeParse(SubordinatesSchema, res); + if (!data.success) { + throw new ValidationError({ + message: 'Invalid response when listing subordinates', + issues: data.issues, + }); + } + + return data.output; + } + + /** + * Gets a subordinate by ID. + */ + async getSubordinateById(id: number): Promise { + const res = await this.#fetch('GET', `/subordinates/${id}`); + + const data = safeParse(SubordinateSchema, res); + if (!data.success) { + throw new ValidationError({ + message: `Invalid response when getting subordinate by id ${id}`, + issues: data.issues, + }); + } + + return data.output; + } + + /** + * @throws {FetchError} + */ + async #fetch(method: HttpMethod, path: string|URL, options: RequestInit & { filters?: Record } = {}): Promise { + const input = new URL(path, this.#apiUrl); + input.pathname = `/api/v1/${input.pathname.replace(/^\/|\/$/g, '')}`; // Prepend api base path. + + if (options.filters) { + for (const [key, value] of Object.entries(options.filters)) { + input.searchParams.append(key, String(value)); + } + + // We don't want to pass this along to the RequestInit. + delete options.filters; + } + + const init: RequestInit = { + ...options, + method, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + ...options.headers, + }, + }; + + try { + const res = await fetch(input, init); + const data = await res.json(); + + if (!res.ok) { + const message = 'message' in data && typeof data.message === 'string' ? data.message : undefined; + throw new FetchError({ status: res.status, message: message }); + } + + return data; + } catch (error) { + if (error instanceof FetchError) throw error; + throw new FetchError({ + message: error instanceof Error ? error.message : 'An unexpected error occurred.', + }); + } + + } +} + diff --git a/admin/frontend/src/lib/icons.ts b/admin/frontend/src/lib/icons.ts new file mode 100644 index 0000000..b28b04f --- /dev/null +++ b/admin/frontend/src/lib/icons.ts @@ -0,0 +1,3 @@ + + + diff --git a/admin/frontend/src/main.ts b/admin/frontend/src/main.ts new file mode 100644 index 0000000..97550b9 --- /dev/null +++ b/admin/frontend/src/main.ts @@ -0,0 +1,21 @@ +import { createApp } from 'vue' +import { createRouter, createWebHistory } from 'vue-router'; +import { AdminSDK } from './lib/admin-sdk'; +import './styles/reset.css'; +import './styles/variables.css'; +import './styles/global.css'; +import { routes } from './routes'; +import App from './App.vue' + +const app = createApp(App); + +app.config.globalProperties.$sdk = new AdminSDK({ + apiUrl: new URL('http://localhost:8000'), +}); + +app.use(createRouter({ + history: createWebHistory(), + routes, +})); + +app.mount('#app'); diff --git a/admin/frontend/src/routes.ts b/admin/frontend/src/routes.ts new file mode 100644 index 0000000..125edb9 --- /dev/null +++ b/admin/frontend/src/routes.ts @@ -0,0 +1,9 @@ +import SubordinatesView from "./views/SubordinatesView.vue"; +import TrustMarksView from "./views/TrustMarksView.vue"; +import TrustMarkTypesView from "./views/TrustMarkTypesView.vue"; + +export const routes = [ + { path: '/trustmark-types', component: TrustMarkTypesView }, + { path: '/trustmarks', component: TrustMarksView }, + { path: '/subordinates', component: SubordinatesView }, +] diff --git a/admin/frontend/src/styles/global.css b/admin/frontend/src/styles/global.css new file mode 100644 index 0000000..fc4b525 --- /dev/null +++ b/admin/frontend/src/styles/global.css @@ -0,0 +1,46 @@ +* { + box-sizing: border-box; +} + +::selection { + background-color: var(--ir--color--black); + color: var(--ir--color--white); +} + +html, +body { + height: 100%; +} + +body { + font-family: var(--ir--font-family); + font-size: var(--ir--font-size); + line-height: var(--ir--line-height); + background-color: var(--ir--color--white); + color: var(--ir--color--black); +} + +body.disable-scroll { + height: auto; + overflow-y: hidden; +} + +picture { + display: block; +} + +img { + width: 100%; + + &[height] { + height: auto; + } +} + +:is(h1, h2, h3, h4, h5) { + font-weight: var(--ir--font-weight--bold); +} + +a { + color: currentColor; +} diff --git a/admin/frontend/src/styles/reset.css b/admin/frontend/src/styles/reset.css new file mode 100644 index 0000000..ed11813 --- /dev/null +++ b/admin/frontend/src/styles/reset.css @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/admin/frontend/src/styles/variables.css b/admin/frontend/src/styles/variables.css new file mode 100644 index 0000000..784ded1 --- /dev/null +++ b/admin/frontend/src/styles/variables.css @@ -0,0 +1,50 @@ +:root { + --ir--font-family: Cantarell, Roboto, sans-serif, system-ui; + + --ir--font-size--xs: .875rem; /* 14px */ + --ir--font-size--s: 1rem; /* 16px */ + --ir--font-size: 1.125rem; /* 18px */ + --ir--font-size--m: 1.5625rem; /* 25px */ + --ir--font-size--l: 2.1875rem; /* 35px */ + --ir--font-size--xl: 3.125rem; /* 50px */ + --ir--font-size--xxl: 3.75rem; /* 60px */ + + --ir--font-weight: 400; + --ir--font-weight--bold: 800; + + --ir--line-height: 1.5; + --ir--line-height--m: 1.3; + --ir--line-height--l: 1.2; + --ir--line-height--xl: 1.1; + --ir--line-height--xxl: 1; + + --ir--space--1: 4px; + --ir--space--2: 8px; + --ir--space--3: 16px; + --ir--space--4: 32px; + --ir--space--5: 40px; + --ir--space--6: 48px; + + --ir--border-radius: var(--ir--space--3); + --ir--border: 1px solid #e2e8f0; + + --ir--color--black: #141414; + --ir--color--white: #ffffff; + + --ir--box-shadow: 0px 5px 17px rgba(89, 89, 89, 0.1); +} + +@media (max-width: 768px) { + :root { + --ir--font-size--l: 1.875rem; /* 30px */ + --ir--font-size--xl: 2.5rem; /* 40px */ + --ir--font-size--xxl: 3.125rem; /* 50px */ + } +} + +@media (max-width: 456px) { + :root { + --ir--font-size--xl: 2.5rem; /* 40px */ + --ir--font-size--xxl: 2.8125rem; /* 45px */ + } +} diff --git a/admin/frontend/src/views/SubordinatesView.vue b/admin/frontend/src/views/SubordinatesView.vue new file mode 100644 index 0000000..82e4c5b --- /dev/null +++ b/admin/frontend/src/views/SubordinatesView.vue @@ -0,0 +1,12 @@ + + + diff --git a/admin/frontend/src/views/TrustMarkTypesView.vue b/admin/frontend/src/views/TrustMarkTypesView.vue new file mode 100644 index 0000000..ab10def --- /dev/null +++ b/admin/frontend/src/views/TrustMarkTypesView.vue @@ -0,0 +1,14 @@ + + + diff --git a/admin/frontend/src/views/TrustMarksView.vue b/admin/frontend/src/views/TrustMarksView.vue new file mode 100644 index 0000000..a48320e --- /dev/null +++ b/admin/frontend/src/views/TrustMarksView.vue @@ -0,0 +1,12 @@ + + + diff --git a/admin/frontend/src/vite-env.d.ts b/admin/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..c10809e --- /dev/null +++ b/admin/frontend/src/vite-env.d.ts @@ -0,0 +1,8 @@ +/// +import { AdminSDK } from "./lib/admin-sdk/sdk"; + +declare module 'vue' { + interface ComponentCustomProperties { + $sdk: AdminSDK + } +} \ No newline at end of file diff --git a/admin/frontend/tsconfig.app.json b/admin/frontend/tsconfig.app.json new file mode 100644 index 0000000..83c9d39 --- /dev/null +++ b/admin/frontend/tsconfig.app.json @@ -0,0 +1,15 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/admin/frontend/tsconfig.json b/admin/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/admin/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/admin/frontend/tsconfig.node.json b/admin/frontend/tsconfig.node.json new file mode 100644 index 0000000..f85a399 --- /dev/null +++ b/admin/frontend/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/admin/frontend/vite.config.ts b/admin/frontend/vite.config.ts new file mode 100644 index 0000000..bbcf80c --- /dev/null +++ b/admin/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], +})