diff --git a/services/ui/package-lock.json b/services/ui/package-lock.json index 032547b..5f1ca75 100644 --- a/services/ui/package-lock.json +++ b/services/ui/package-lock.json @@ -8,8 +8,14 @@ "name": "subwatcher-ui", "version": "1.0.0", "dependencies": { + "@mantine/core": "^8.3.15", + "@mantine/hooks": "^8.3.15", + "@mantine/modals": "^8.3.15", + "@mantine/notifications": "^8.3.15", + "@tabler/icons-react": "^3.37.1", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^7.13.1" }, "devDependencies": { "@types/react": "^18.3.18", @@ -221,6 +227,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "dev": true, @@ -263,6 +278,278 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.25.12", "cpu": [ @@ -278,6 +565,212 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.18", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.18.tgz", + "integrity": "sha512-xJWJxvmy3a05j643gQt+pRbht5XnTlGpsEsAPnMi5F5YTOEEJymA90uZKBD8OvIv5XvZ1qi4GcccSlqT3Bq44Q==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.7", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "dev": true, @@ -318,11 +811,314 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mantine/core": { + "version": "8.3.15", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.15.tgz", + "integrity": "sha512-wBn/GogB4x7a2Uj7Ztt3amRaApjED+9XqfE4wyCLh88R7KV55k9vnTdCx+irI/GLOOu9tXNUGm3a4t5sTajwkQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.16", + "clsx": "^2.1.1", + "react-number-format": "^5.4.4", + "react-remove-scroll": "^2.7.1", + "react-textarea-autosize": "8.5.9", + "type-fest": "^4.41.0" + }, + "peerDependencies": { + "@mantine/hooks": "8.3.15", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/hooks": { + "version": "8.3.15", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.15.tgz", + "integrity": "sha512-AUSnpUlzttHzJht3CJ1YWi16iy6NWRwtyWO5RLGHHsmiW05DyG0qOPKF8+R5dLHuOCnl3XOu4roI2Y1ku9U04Q==", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/modals": { + "version": "8.3.15", + "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-8.3.15.tgz", + "integrity": "sha512-2071LNa203BX0S/rgn0Q0v9H5ou+3qM4O+6tzYRqiNweQLWDUyIwQRjcWTm64X7qORRWl5IFzgp5hySLhCFfGw==", + "license": "MIT", + "peerDependencies": { + "@mantine/core": "8.3.15", + "@mantine/hooks": "8.3.15", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/notifications": { + "version": "8.3.15", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-8.3.15.tgz", + "integrity": "sha512-CJGSv8oeLWyJIVPninU7Ud6vV6/UJKWZJwRGBNg2K0Ak0U0coFN3gW3H6G1Mh2zllNxb3K4fpMJNz4Iy0sCBFw==", + "license": "MIT", + "dependencies": { + "@mantine/store": "8.3.15", + "react-transition-group": "4.4.5" + }, + "peerDependencies": { + "@mantine/core": "8.3.15", + "@mantine/hooks": "8.3.15", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/store": { + "version": "8.3.15", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-8.3.15.tgz", + "integrity": "sha512-wdx91a73dM2G02YPIZ9i5UXPWfvjdf3qPAwSGnSsBFQg5uM/5CcPAOOQwlYIkvX1edUA5BFOk/4IjpEXSYUDeQ==", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "dev": true, "license": "MIT" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.57.1", "cpu": [ @@ -335,6 +1131,130 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tabler/icons": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.37.1.tgz", + "integrity": "sha512-neLCWkuyNHEPXCyYu6nbN4S3g/59BTa4qyITAugYVpq1YzYNDOZooW7/vRWH98ZItXAudxdKU8muFT7y1PqzuA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.37.1.tgz", + "integrity": "sha512-R7UE71Jji7i4Su56Y9zU1uYEBakUejuDJvyuYVmBuUoqp/x3Pn4cv2huarexR3P0GJ2eHg4rUj9l5zccqS6K/Q==", + "license": "MIT", + "dependencies": { + "@tabler/icons": "" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "dev": true, @@ -379,12 +1299,12 @@ }, "node_modules/@types/prop-types": { "version": "15.7.15", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.28", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -477,14 +1397,35 @@ ], "license": "CC-BY-4.0" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/csstype": { "version": "3.2.3", - "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -503,6 +1444,22 @@ } } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.286", "dev": true, @@ -572,6 +1529,21 @@ } } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "dev": true, @@ -580,6 +1552,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -651,6 +1632,15 @@ "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "dev": true, @@ -694,6 +1684,17 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/react": { "version": "18.3.1", "license": "MIT", @@ -715,6 +1716,22 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-number-format": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz", + "integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "dev": true, @@ -723,6 +1740,146 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", + "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/rollup": { "version": "4.57.1", "dev": true, @@ -781,6 +1938,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "dev": true, @@ -789,6 +1952,12 @@ "node": ">=0.10.0" } }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "dev": true, @@ -804,6 +1973,24 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "dev": true, @@ -845,6 +2032,94 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/vite": { "version": "6.4.1", "dev": true, diff --git a/services/ui/package.json b/services/ui/package.json index 68e85b1..7f2f918 100644 --- a/services/ui/package.json +++ b/services/ui/package.json @@ -9,8 +9,14 @@ "start": "serve -s dist -l 3000" }, "dependencies": { + "@mantine/core": "^8.3.15", + "@mantine/hooks": "^8.3.15", + "@mantine/modals": "^8.3.15", + "@mantine/notifications": "^8.3.15", + "@tabler/icons-react": "^3.37.1", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^7.13.1" }, "devDependencies": { "@types/react": "^18.3.18", @@ -19,4 +25,4 @@ "typescript": "^5.7.3", "vite": "^6.0.11" } -} \ No newline at end of file +} diff --git a/services/ui/src/App.tsx b/services/ui/src/App.tsx index 5f203de..2f08bb1 100644 --- a/services/ui/src/App.tsx +++ b/services/ui/src/App.tsx @@ -1,5 +1,7 @@ -import { useState } from 'react'; -import { Layout, Tab } from './components/Layout'; +import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; +import { Alert, Button, Stack } from '@mantine/core'; +import { IconArrowLeft, IconAlertTriangle } from '@tabler/icons-react'; +import { Layout } from './components/Layout'; import { DashboardPage } from './pages/DashboardPage'; import { JobsPage } from './pages/JobsPage'; import { JobDetailPage } from './pages/JobDetailPage'; @@ -8,27 +10,44 @@ import { SettingsPage } from './pages/SettingsPage'; import { WatchedPathsPage } from './pages/WatchedPathsPage'; export default function App() { - const [tab, setTab] = useState('dashboard'); - const [selectedJob, setSelectedJob] = useState(null); + const navigate = useNavigate(); + + const handleSelectJob = (jobId: string) => { + navigate(`/jobs/${jobId}`); + }; return ( - - {selectedJob && ( -
- -
- )} - {selectedJob ? ( - - ) : ( - <> - {tab === 'dashboard' && } - {tab === 'jobs' && } - {tab === 'review' && } - {tab === 'settings' && } - {tab === 'paths' && } - - )} + + + } /> + } /> + } /> + } /> + } /> + } /> + ); } + +function JobDetailRoute() { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + + if (!id) { + return ( + }> + Geçersiz iş id. + + ); + } + + return ( + + + + + ); +} diff --git a/services/ui/src/api/client.ts b/services/ui/src/api/client.ts index 7e2805f..073bfeb 100644 --- a/services/ui/src/api/client.ts +++ b/services/ui/src/api/client.ts @@ -1,9 +1,29 @@ -export async function api(path: string, options?: RequestInit): Promise { +function getCoreBaseUrl(): string { const base = import.meta.env.VITE_PUBLIC_CORE_URL || 'http://localhost:3001'; - const res = await fetch(`${base}${path}`, { + return base.replace(/\/+$/, ''); +} + +function normalizePath(path: string): string { + return path.startsWith('/') ? path : `/${path}`; +} + +export function buildApiUrl(path: string): string { + return `${getCoreBaseUrl()}${normalizePath(path)}`; +} + +export function buildSseUrl(path: string): string { + return buildApiUrl(path); +} + +export async function api(path: string, options?: RequestInit): Promise { + const res = await fetch(buildApiUrl(path), { headers: { 'content-type': 'application/json', ...(options?.headers || {}) }, ...options }); - if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); + + if (!res.ok) { + throw new Error(`${res.status} ${res.statusText}`); + } + return res.json(); } diff --git a/services/ui/src/components/JobTable.tsx b/services/ui/src/components/JobTable.tsx index e724edd..fe81d0f 100644 --- a/services/ui/src/components/JobTable.tsx +++ b/services/ui/src/components/JobTable.tsx @@ -1,26 +1,50 @@ +import { Badge, Paper, ScrollArea, Table, Text } from '@mantine/core'; import { Job } from '../types'; +import { statusColor } from '../lib/status'; export function JobTable({ jobs, onSelect }: { jobs: Job[]; onSelect: (id: string) => void }) { return ( - - - - - - - - - - - {jobs.map((j) => ( - onSelect(j._id)} style={{ cursor: 'pointer', borderTop: '1px solid #e2e8f0' }}> - - - - - - ))} - -
IDDurumBaslikGuncelleme
{j._id.slice(-8)}{j.status}{j.requestSnapshot?.title || '-'}{new Date(j.updatedAt).toLocaleString()}
+ + + + + + ID + Durum + Başlık + Güncelleme + + + + {jobs.length === 0 ? ( + + + + Kayıt bulunamadı. + + + + ) : ( + jobs.map((job) => ( + onSelect(job._id)}> + + + {job._id.slice(-8)} + + + + + {job.status} + + + {job.requestSnapshot?.title || job.requestSnapshot?.path || '-'} + {new Date(job.updatedAt).toLocaleString()} + + )) + )} + +
+
+
); } diff --git a/services/ui/src/components/Layout.tsx b/services/ui/src/components/Layout.tsx index eba60bf..d669126 100644 --- a/services/ui/src/components/Layout.tsx +++ b/services/ui/src/components/Layout.tsx @@ -1,20 +1,118 @@ -import React from 'react'; +import { useState } from 'react'; +import { AppShell, Box, Burger, Group, NavLink, Stack, Text } from '@mantine/core'; +import { + IconChartDonut3, + IconListDetails, + IconMessage2Exclamation, + IconFolderSearch, + IconAdjustmentsCog, + IconRadar2 +} from '@tabler/icons-react'; +import { useLocation, useNavigate } from 'react-router-dom'; -const tabs = ['dashboard', 'jobs', 'review', 'settings', 'paths'] as const; -export type Tab = (typeof tabs)[number]; +interface MenuItem { + label: string; + path: string; + icon: typeof IconChartDonut3; +} + +const items: MenuItem[] = [ + { label: 'Dashboard', path: '/', icon: IconChartDonut3 }, + { label: 'Jobs', path: '/jobs', icon: IconListDetails }, + { label: 'Review', path: '/review', icon: IconMessage2Exclamation }, + { label: 'Watched Paths', path: '/paths', icon: IconFolderSearch }, + { label: 'Settings', path: '/settings', icon: IconAdjustmentsCog } +]; + +export function Layout({ children }: { children: React.ReactNode }) { + const [opened, setOpened] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); -export function Layout({ tab, setTab, children }: { tab: Tab; setTab: (t: Tab) => void; children: React.ReactNode }) { return ( -
-
- subwatcher - {tabs.map((t) => ( - - ))} -
-
{children}
-
+ + + + + setOpened((s) => !s)} hiddenFrom="sm" size="sm" /> + + +
+ + SubWatcher Control + + + subtitle automation cockpit + +
+
+
+
+
+ + + + {items.map((item) => { + const active = item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path); + return ( + } + onClick={() => { + navigate(item.path); + setOpened(false); + }} + styles={{ + root: { + borderRadius: 10, + color: '#233548' + }, + body: { + fontFamily: 'Sora, sans-serif', + letterSpacing: '0.02em' + } + }} + /> + ); + })} + + + + + Live Pipeline + + + Durumlar panelden canlı izlenir, hata durumları review ekranına düşer. + + + + + {children} +
); } diff --git a/services/ui/src/index.css b/services/ui/src/index.css new file mode 100644 index 0000000..40e6028 --- /dev/null +++ b/services/ui/src/index.css @@ -0,0 +1,47 @@ +@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600&display=swap'); + +:root { + --sw-bg-0: #f7fbff; + --sw-bg-1: #f1f6fd; + --sw-bg-2: #e9f1fb; + --sw-accent: #0ea5a3; + --sw-accent-2: #1d4ed8; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + min-height: 100%; +} + +body { + margin: 0; + background: + radial-gradient(circle at 10% 12%, rgba(14, 165, 163, 0.14), transparent 32%), + radial-gradient(circle at 88% 10%, rgba(29, 78, 216, 0.1), transparent 34%), + linear-gradient(160deg, var(--sw-bg-0), var(--sw-bg-1) 48%, var(--sw-bg-2)); + color: #1c2733; + font-family: 'Source Serif 4', serif; +} + +body::before { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + background-image: linear-gradient(rgba(14, 23, 38, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(14, 23, 38, 0.03) 1px, transparent 1px); + background-size: 36px 36px; + opacity: 0.5; +} + +.sw-page-title { + font-family: 'Sora', sans-serif; + letter-spacing: 0.02em; + margin: 0; + color: #102032; +} diff --git a/services/ui/src/lib/status.ts b/services/ui/src/lib/status.ts new file mode 100644 index 0000000..7654987 --- /dev/null +++ b/services/ui/src/lib/status.ts @@ -0,0 +1,23 @@ +export const JOB_STATUSES = [ + 'PENDING', + 'WAITING_FILE_STABLE', + 'PARSED', + 'ANALYZED', + 'REQUESTING_API', + 'FOUND_TEMP', + 'NORMALIZING_ENCODING', + 'WRITING_SUBTITLE', + 'DONE', + 'NEEDS_REVIEW', + 'NOT_FOUND', + 'AMBIGUOUS', + 'ERROR' +] as const; + +export function statusColor(status: string): string { + if (status === 'DONE') return 'teal'; + if (status === 'NEEDS_REVIEW') return 'yellow'; + if (status === 'ERROR') return 'red'; + if (status === 'NOT_FOUND' || status === 'AMBIGUOUS') return 'orange'; + return 'blue'; +} diff --git a/services/ui/src/main.tsx b/services/ui/src/main.tsx index 9707d82..a7bf2e5 100644 --- a/services/ui/src/main.tsx +++ b/services/ui/src/main.tsx @@ -1,9 +1,46 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { createTheme, MantineProvider } from '@mantine/core'; +import { ModalsProvider } from '@mantine/modals'; +import { Notifications } from '@mantine/notifications'; import App from './App'; +import '@mantine/core/styles.css'; +import '@mantine/notifications/styles.css'; +import './index.css'; + +const theme = createTheme({ + primaryColor: 'cyan', + defaultRadius: 'md', + fontFamily: '"Source Serif 4", serif', + headings: { + fontFamily: '"Sora", sans-serif' + }, + colors: { + shell: [ + '#ffffff', + '#f7fbff', + '#eef5ff', + '#dde9f7', + '#ccdaec', + '#b4c4d8', + '#8fa0b3', + '#6f7f90', + '#556473', + '#3a4651' + ] + } +}); ReactDOM.createRoot(document.getElementById('root')!).render( - + + + + + + + + ); diff --git a/services/ui/src/pages/DashboardPage.tsx b/services/ui/src/pages/DashboardPage.tsx index 6326037..d1f6747 100644 --- a/services/ui/src/pages/DashboardPage.tsx +++ b/services/ui/src/pages/DashboardPage.tsx @@ -1,4 +1,6 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; +import { Badge, Card, Group, SimpleGrid, Stack, Text } from '@mantine/core'; +import { IconAlertTriangle, IconChecklist, IconClockHour4, IconProgressCheck } from '@tabler/icons-react'; import { api } from '../api/client'; import { Job } from '../types'; import { usePoll } from '../hooks/usePoll'; @@ -8,37 +10,88 @@ export function DashboardPage({ onSelectJob }: { onSelectJob: (id: string) => vo const [jobs, setJobs] = useState([]); const load = useCallback(async () => { - const data = await api<{ items: Job[] }>('/api/jobs?limit=20'); + const data = await api<{ items: Job[] }>('/api/jobs?limit=30'); setJobs(data.items); }, []); usePoll(load, 5000); - const since = Date.now() - 24 * 3600 * 1000; - const recent = jobs.filter((x) => new Date(x.createdAt).getTime() >= since); - const done = recent.filter((x) => x.status === 'DONE').length; - const review = recent.filter((x) => x.status === 'NEEDS_REVIEW').length; - const errors = recent.filter((x) => x.status === 'ERROR').length; + const stats = useMemo(() => { + const since = Date.now() - 24 * 3600 * 1000; + const recent = jobs.filter((x) => new Date(x.createdAt).getTime() >= since); + return { + recent, + total: recent.length, + done: recent.filter((x) => x.status === 'DONE').length, + review: recent.filter((x) => x.status === 'NEEDS_REVIEW').length, + error: recent.filter((x) => x.status === 'ERROR').length + }; + }, [jobs]); return ( -
-
- - - - -
-

Son Isler

- -
+ + +
+

Operations Dashboard

+ Son 24 saatin özetini ve son işleri buradan takip et. +
+ + Live polling: 5s + +
+ + + } /> + } /> + } /> + } /> + + + + + + Son İşler + + + Liste otomatik yenilenir. + + + + +
); } -function Stat({ label, value }: { label: string; value: string }) { +function StatCard({ + title, + value, + color, + icon +}: { + title: string; + value: number; + color: string; + icon: React.ReactNode; +}) { return ( -
-
{label}
-
{value}
-
+ + + + {title} + +
{icon}
+
+ + {value} + +
); } diff --git a/services/ui/src/pages/JobDetailPage.tsx b/services/ui/src/pages/JobDetailPage.tsx index 007408a..72bb22f 100644 --- a/services/ui/src/pages/JobDetailPage.tsx +++ b/services/ui/src/pages/JobDetailPage.tsx @@ -1,25 +1,54 @@ import { useEffect, useState } from 'react'; -import { api } from '../api/client'; -import { Job, JobLog } from '../types'; +import { + Alert, + Badge, + Button, + Card, + Divider, + Group, + NumberInput, + ScrollArea, + SimpleGrid, + Stack, + Text, + TextInput +} from '@mantine/core'; +import { notifications } from '@mantine/notifications'; +import { IconCheck, IconSparkles, IconTerminal2 } from '@tabler/icons-react'; +import { api, buildSseUrl } from '../api/client'; +import { Job, JobCandidate, JobLog } from '../types'; +import { statusColor } from '../lib/status'; + +interface ManualOverride { + title?: string; + year?: number; + release?: string; + season?: number; + episode?: number; +} export function JobDetailPage({ jobId }: { jobId: string }) { const [job, setJob] = useState(null); const [logs, setLogs] = useState([]); - const [override, setOverride] = useState({}); - const [candidates, setCandidates] = useState([]); + const [override, setOverride] = useState({}); + const [candidates, setCandidates] = useState([]); useEffect(() => { let es: EventSource | null = null; (async () => { - const j = await api(`/api/jobs/${jobId}`); - setJob(j); - const l = await api<{ items: JobLog[] }>(`/api/jobs/${jobId}/logs?limit=200`); - setLogs(l.items); - if (j.apiSnapshot?.candidates) setCandidates(j.apiSnapshot.candidates); + const loadedJob = await api(`/api/jobs/${jobId}`); + setJob(loadedJob); - es = new EventSource(`/api/jobs/${jobId}/stream`); - es.onmessage = (ev) => { - const item = JSON.parse(ev.data); + const loadedLogs = await api<{ items: JobLog[] }>(`/api/jobs/${jobId}/logs?limit=200`); + setLogs(loadedLogs.items); + + if (loadedJob.apiSnapshot?.candidates) { + setCandidates(loadedJob.apiSnapshot.candidates); + } + + es = new EventSource(buildSseUrl(`/api/jobs/${jobId}/stream`)); + es.onmessage = (event) => { + const item = JSON.parse(event.data) as JobLog; setLogs((prev) => [...prev, item]); }; })(); @@ -30,66 +59,173 @@ export function JobDetailPage({ jobId }: { jobId: string }) { }, [jobId]); async function manualSearch() { - const res = await api(`/api/review/${jobId}/search`, { method: 'POST', body: JSON.stringify(override) }); - setCandidates(res.candidates || []); + try { + const res = await api<{ candidates?: JobCandidate[] }>(`/api/review/${jobId}/search`, { + method: 'POST', + body: JSON.stringify(override) + }); + const list = res.candidates || []; + setCandidates(list); + notifications.show({ + color: list.length > 0 ? 'teal' : 'yellow', + title: 'Arama tamamlandı', + message: list.length > 0 ? `${list.length} aday bulundu.` : 'Aday bulunamadı.' + }); + } catch (error) { + notifications.show({ color: 'red', title: 'Hata', message: (error as Error).message || 'Arama başarısız.' }); + } } - async function choose(c: any) { - await api(`/api/review/${jobId}/choose`, { method: 'POST', body: JSON.stringify({ chosenCandidateId: c.id, lang: c.lang || 'tr' }) }); - const j = await api(`/api/jobs/${jobId}`); - setJob(j); + async function choose(candidate: JobCandidate) { + try { + await api(`/api/review/${jobId}/choose`, { + method: 'POST', + body: JSON.stringify({ chosenCandidateId: candidate.id, lang: candidate.lang || 'tr' }) + }); + + const refreshed = await api(`/api/jobs/${jobId}`); + setJob(refreshed); + notifications.show({ color: 'teal', title: 'Seçim kaydedildi', message: 'Aday başarıyla uygulandı.' }); + } catch (error) { + notifications.show({ color: 'red', title: 'Hata', message: (error as Error).message || 'Aday seçilemedi.' }); + } } - if (!job) return
Yukleniyor...
; + if (!job) return Yükleniyor...; + + const media = job.mediaFileId; - const media = job.mediaFileId as any; return ( -
-

Job #{job._id.slice(-8)} - {job.status}

-
-
Baslik: {job.requestSnapshot?.title || '-'}
-
Tip: {job.requestSnapshot?.type || '-'}
-
Yil: {job.requestSnapshot?.year || '-'}
-
Release: {job.requestSnapshot?.release || '-'}
-
Season/Episode: {job.requestSnapshot?.season ?? '-'} / {job.requestSnapshot?.episode ?? '-'}
-
Media: {media?.path || '-'}
-
Video: {media?.mediaInfo?.video?.codec_name || '-'} {media?.mediaInfo?.video?.width || '-'}x{media?.mediaInfo?.video?.height || '-'}
-
Sonuc: {job.result?.subtitles?.map((s: any) => s.writtenPath).join(', ') || '-'}
-
- - {job.status === 'NEEDS_REVIEW' && ( -
-

Manual Override

-
- setOverride((x: any) => ({ ...x, title: e.target.value }))} /> - setOverride((x: any) => ({ ...x, year: Number(e.target.value) }))} /> - setOverride((x: any) => ({ ...x, release: e.target.value }))} /> - setOverride((x: any) => ({ ...x, season: Number(e.target.value) }))} /> - setOverride((x: any) => ({ ...x, episode: Number(e.target.value) }))} /> - -
-
    - {candidates.map((c) => ( -
  • - {c.provider} | {c.id} | score={c.score} - -
  • - ))} -
+ + +
+

Job #{job._id.slice(-8)}

+ {job.requestSnapshot?.path || 'manual trigger'}
- )} + + {job.status} + +
-
-

Canli Loglar

-
- {logs.map((l) => ( -
- [{new Date(l.ts).toLocaleTimeString()}] {l.step} - {l.message} - {l.meta ? ` | meta=${JSON.stringify(l.meta)}` : ''} -
- ))} -
-
+ + + + + + + + + + item.writtenPath).join(', ') || '-'} /> + + + + {job.status === 'NEEDS_REVIEW' ? ( + + + + + + Manual Override + + + + + setOverride((prev) => ({ ...prev, title: e.currentTarget.value }))} /> + setOverride((prev) => ({ ...prev, year: Number(value) }))} hideControls /> + setOverride((prev) => ({ ...prev, release: e.currentTarget.value }))} /> + setOverride((prev) => ({ ...prev, season: Number(value) }))} hideControls /> + setOverride((prev) => ({ ...prev, episode: Number(value) }))} hideControls /> + + + + {candidates.length === 0 ? ( + + Aday bulunamadı. + + ) : ( + + {candidates.map((candidate) => ( + +
+ {candidate.provider} + + {candidate.id} • score {candidate.score} + +
+ +
+ ))} +
+ )} +
+
+ ) : null} + + + + + + + Canlı Log Akışı + + + + + + + + {logs.length === 0 ? ( + + Henüz log yok. + + ) : ( + logs.map((log) => ( + + + + [{new Date(log.ts).toLocaleTimeString()}] + +
+ + {log.step} + + {log.message} + {log.meta ? ( + + {JSON.stringify(log.meta)} + + ) : null} +
+
+
+ )) + )} +
+
+
+
+
+ ); +} + +function Detail({ label, value }: { label: string; value: string }) { + return ( +
+ + {label} + + {value}
); } diff --git a/services/ui/src/pages/JobsPage.tsx b/services/ui/src/pages/JobsPage.tsx index d1bb384..c38fe9d 100644 --- a/services/ui/src/pages/JobsPage.tsx +++ b/services/ui/src/pages/JobsPage.tsx @@ -1,18 +1,21 @@ import { useCallback, useState } from 'react'; +import { Button, Group, Select, Stack, Text, TextInput } from '@mantine/core'; +import { IconFilter, IconSearch } from '@tabler/icons-react'; import { api } from '../api/client'; import { Job } from '../types'; import { usePoll } from '../hooks/usePoll'; import { JobTable } from '../components/JobTable'; +import { JOB_STATUSES } from '../lib/status'; export function JobsPage({ onSelectJob }: { onSelectJob: (id: string) => void }) { const [jobs, setJobs] = useState([]); - const [status, setStatus] = useState(''); + const [status, setStatus] = useState(null); const [search, setSearch] = useState(''); const load = useCallback(async () => { const q = new URLSearchParams({ limit: '100' }); if (status) q.set('status', status); - if (search) q.set('search', search); + if (search.trim()) q.set('search', search.trim()); const data = await api<{ items: Job[] }>(`/api/jobs?${q.toString()}`); setJobs(data.items); }, [status, search]); @@ -20,13 +23,39 @@ export function JobsPage({ onSelectJob }: { onSelectJob: (id: string) => void }) usePoll(load, 4000); return ( -
-
- setStatus(e.target.value)} /> - setSearch(e.target.value)} /> - + +
+

Jobs Explorer

+ Durum ve metin filtresiyle işleri detaylı inceleyin.
+ + + setSettings({ ...settings, languages: e.target.value.split(',').map((x) => x.trim()).filter(Boolean) })} /> - - - - - - - - - -
+ +
+

Settings

+ Arama, stabilite ve yazma davranışlarını buradan yönetin. +
+ + + + + setSettings({ + ...settings, + languages: event.currentTarget.value + .split(',') + .map((item) => item.trim()) + .filter(Boolean) + }) + } + /> + + + + setSettings({ ...settings, multiSubtitleEnabled: event.currentTarget.checked })} + /> + + + setSettings({ ...settings, overwriteExisting: event.currentTarget.checked })} + /> + + + setSettings({ ...settings, preferHI: event.currentTarget.checked })} + /> + + + setSettings({ ...settings, preferForced: event.currentTarget.checked })} + /> + + + + + + setSettings({ ...settings, stableChecks: Number(value) })} + /> + + + setSettings({ ...settings, stableIntervalSeconds: Number(value) })} + /> + + + setSettings({ ...settings, autoWriteThreshold: Number(value) })} + /> + + + + + + + + +
); } diff --git a/services/ui/src/pages/WatchedPathsPage.tsx b/services/ui/src/pages/WatchedPathsPage.tsx index e24a92b..b2ed5ce 100644 --- a/services/ui/src/pages/WatchedPathsPage.tsx +++ b/services/ui/src/pages/WatchedPathsPage.tsx @@ -1,60 +1,160 @@ import { useCallback, useState } from 'react'; +import { + ActionIcon, + Badge, + Button, + Group, + Paper, + ScrollArea, + Select, + Stack, + Table, + Text, + TextInput +} from '@mantine/core'; +import { modals } from '@mantine/modals'; +import { notifications } from '@mantine/notifications'; +import { IconPlus, IconTrash, IconToggleLeft, IconToggleRight } from '@tabler/icons-react'; import { api } from '../api/client'; import { usePoll } from '../hooks/usePoll'; +import { WatchedPath } from '../types'; export function WatchedPathsPage() { - const [items, setItems] = useState([]); + const [items, setItems] = useState([]); const [path, setPath] = useState(''); - const [kind, setKind] = useState('mixed'); + const [kind, setKind] = useState('mixed'); const load = useCallback(async () => { - const data = await api('/api/watched-paths'); - setItems(data); + const data = await api('/api/watched-paths'); + setItems(data || []); }, []); usePoll(load, 5000); async function add() { - await api('/api/watched-paths', { method: 'POST', body: JSON.stringify({ action: 'add', path, kind }) }); - setPath(''); - await load(); + if (!path.trim()) return; + try { + await api('/api/watched-paths', { method: 'POST', body: JSON.stringify({ action: 'add', path: path.trim(), kind }) }); + setPath(''); + notifications.show({ color: 'teal', title: 'Eklendi', message: 'Watched path başarıyla eklendi.' }); + await load(); + } catch (error) { + notifications.show({ color: 'red', title: 'Hata', message: (error as Error).message || 'Path eklenemedi.' }); + } } - async function toggle(item: any) { - await api('/api/watched-paths', { method: 'POST', body: JSON.stringify({ action: 'toggle', path: item.path, enabled: !item.enabled }) }); - await load(); + async function toggle(item: WatchedPath) { + try { + await api('/api/watched-paths', { + method: 'POST', + body: JSON.stringify({ action: 'toggle', path: item.path, enabled: !item.enabled }) + }); + notifications.show({ color: 'cyan', title: 'Güncellendi', message: `${item.path} durumu değiştirildi.` }); + await load(); + } catch (error) { + notifications.show({ color: 'red', title: 'Hata', message: (error as Error).message || 'Durum güncellenemedi.' }); + } } - async function remove(item: any) { - await api('/api/watched-paths', { method: 'POST', body: JSON.stringify({ action: 'remove', path: item.path }) }); - await load(); + function remove(item: WatchedPath) { + modals.openConfirmModal({ + title: 'Path silinsin mi?', + centered: true, + children: {item.path} izleme listesinden çıkarılacak., + labels: { confirm: 'Sil', cancel: 'Vazgeç' }, + confirmProps: { color: 'red' }, + onConfirm: async () => { + try { + await api('/api/watched-paths', { method: 'POST', body: JSON.stringify({ action: 'remove', path: item.path }) }); + notifications.show({ color: 'teal', title: 'Silindi', message: 'Watched path kaldırıldı.' }); + await load(); + } catch (error) { + notifications.show({ color: 'red', title: 'Hata', message: (error as Error).message || 'Path silinemedi.' }); + } + } + }); } return ( -
-
- setPath(e.target.value)} /> - - + +
+

Watched Paths

+ Core watcher’ın izleyeceği dizinleri buradan yönetin.
- - - - {items.map((i) => ( - - - - - ))} - -
PathKindEnabled
{i.path}{i.kind}{String(i.enabled)} - - -
-
+ + + setPath(event.currentTarget.value)} + w={420} + /> +