diff --git a/.env.example b/.env.example index 6721966..fed4a5b 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ # App PORT=3000 NODE_ENV=development +BUN_PUBLIC_BASE_URL=http://localhost:3000 # Dev Inspector REACT_EDITOR=code @@ -13,12 +14,20 @@ DIRECT_URL=postgresql://user:password@localhost:5432/base-template GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= -# Role +# Super Admin (comma-separated emails) SUPER_ADMIN_EMAIL=admin@example.com # API Key for external clients (e.g. mobile apps) API_KEY=your-secret-api-key-here -# Telegram Notification (optional) -TELEGRAM_NOTIFY_TOKEN= -TELEGRAM_NOTIFY_CHAT_ID= +# MinIO (object storage for bug report images) +MINIO_ENDPOINT= +MINIO_PORT=443 +MINIO_USE_SSL=true +MINIO_ACCESS_KEY= +MINIO_SECRET_KEY= +MINIO_BUCKET= +MINIO_UPLOAD_DIR=bug-reports + +# Redis (optional — enables App Logs feature on /dev) +REDIS_URL= diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..2d35436 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,16 @@ +{ + "mcpServers": { + "deploy-stg": { + "type": "stdio", + "command": "bun", + "args": ["scripts/mcp-deploy.ts"], + "env": { + "GH_TOKEN": "${GH_TOKEN}", + "STACK_NAME": "monitoring-app", + "BASE_URL": "https://api.github.com/repos/bipprojectbali/monitoring-app", + "STG_URL": "https://monitoring-stg.wibudev.com", + "VERSION_PATH": "/api/system/version" + } + } + } +} diff --git a/Dockerfile b/Dockerfile index 923dcd9..90fbed2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,8 @@ RUN bunx prisma generate # Build frontend (Vite → dist/) FROM prisma AS builder +ARG VITE_URL_API_DESA_PLUS +ENV VITE_URL_API_DESA_PLUS=$VITE_URL_API_DESA_PLUS COPY . . RUN bun run build diff --git a/bun.lock b/bun.lock index 6635a06..c1d46c1 100644 --- a/bun.lock +++ b/bun.lock @@ -10,13 +10,16 @@ "@elysiajs/swagger": "^1.3.1", "@mantine/charts": "^9.0.0", "@mantine/core": "^8.3.18", + "@mantine/dates": "^9.1.1", "@mantine/hooks": "^8.3.18", "@mantine/modals": "^8.3.18", "@mantine/notifications": "^8.3.18", + "@modelcontextprotocol/sdk": "^1.29.0", "@prisma/client": "6", "@tanstack/react-query": "^5.95.2", "@tanstack/react-router": "^1.168.10", "@xyflow/react": "^12.6.4", + "dayjs": "^1.11.20", "elkjs": "^0.9.3", "elysia": "^1.4.28", "minio": "^8.0.7", @@ -183,6 +186,8 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -201,6 +206,8 @@ "@mantine/core": ["@mantine/core@8.3.18", "", { "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.18", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-9tph1lTVogKPjTx02eUxDUOdXacPzK62UuSqb4TdGliI54/Xgxftq0Dfqu6XuhCxn9J5MDJaNiLDvL/1KRkYqA=="], + "@mantine/dates": ["@mantine/dates@9.1.1", "", { "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { "@mantine/core": "9.1.1", "@mantine/hooks": "9.1.1", "dayjs": ">=1.0.0", "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-P1tr/Hr+EVxppbOVpTLvaZZnM1W/r0TNpqNNMeM81xfyuKYzd7zt2/SQYb6BuudgEQfRJnAee+7bIJLEsrb0uA=="], + "@mantine/hooks": ["@mantine/hooks@8.3.18", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-QoWr9+S8gg5050TQ06aTSxtlpGjYOpIllRbjYYXlRvZeTsUqiTbVfvQROLexu4rEaK+yy9Wwriwl9PMRgbLqPw=="], "@mantine/modals": ["@mantine/modals@8.3.18", "", { "peerDependencies": { "@mantine/core": "8.3.18", "@mantine/hooks": "8.3.18", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-JfPDS4549L314SxFPC1x6CbKwzh82OdnIzwgMxPCVNsWLKV2vEHHUH/fzUYj4Wli6IBrsW4cufjMj9BTj3hm3Q=="], @@ -209,6 +216,8 @@ "@mantine/store": ["@mantine/store@8.3.18", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-i+QRTLmZzLldea0egtUVnGALd6UMIu8jd44nrNWBSNIXJU/8B6rMlC6gyX+l4szopZSuOaaNJIXkqRdC1gQsVg=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], @@ -359,10 +368,16 @@ "@xyflow/system": ["@xyflow/system@0.0.76", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -399,6 +414,8 @@ "block-stream2": ["block-stream2@2.1.0", "", { "dependencies": { "readable-stream": "^3.4.0" } }, "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg=="], + "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "browser-or-node": ["browser-or-node@2.1.1", "", {}, "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg=="], @@ -409,8 +426,14 @@ "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], "caniuse-lite": ["caniuse-lite@1.0.30001782", "", {}, "sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw=="], @@ -437,12 +460,22 @@ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="], + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -481,6 +514,8 @@ "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], + "dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], @@ -493,6 +528,8 @@ "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], @@ -509,6 +546,10 @@ "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="], "electron-to-chromium": ["electron-to-chromium@1.5.329", "", {}, "sha512-/4t+AS1l4S3ZC0Ja7PHFIWeBIxGA3QGqV8/yKsP36v7NcyUCl+bIcmw6s5zVuMIECWwBrAK/6QLzTmbJChBboQ=="], @@ -521,14 +562,24 @@ "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="], "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], @@ -537,12 +588,22 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], + "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + "exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="], + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + + "express-rate-limit": ["express-rate-limit@8.4.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw=="], + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], @@ -551,8 +612,12 @@ "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fast-xml-builder": ["fast-xml-builder@1.1.5", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA=="], "fast-xml-parser": ["fast-xml-parser@5.7.1", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.5", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA=="], @@ -567,16 +632,28 @@ "filter-obj": ["filter-obj@1.1.0", "", {}, "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="], + "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="], @@ -587,12 +664,24 @@ "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "hono": ["hono@4.12.15", "", {}, "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg=="], + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], @@ -615,14 +704,24 @@ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "isbot": ["isbot@5.1.37", "", {}, "sha512-5bcicX81xf6NlTEV8rWdg7Pk01LFizDetuYGHx6d/f6y3lR2/oo8IfxjzJqn1UdDEyCcwT9e7NRloj8DwCYujQ=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -655,8 +754,14 @@ "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -669,6 +774,8 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], @@ -681,8 +788,12 @@ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], @@ -691,8 +802,14 @@ "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], @@ -703,6 +820,8 @@ "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], "playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="], @@ -731,6 +850,8 @@ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], @@ -741,8 +862,14 @@ "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], + "query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], @@ -781,24 +908,48 @@ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "rolldown": ["rolldown@1.0.0-rc.12", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.12" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-x64": "1.0.0-rc.12", "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + "seroval": ["seroval@1.5.1", "", {}, "sha512-OwrZRZAfhHww0WEnKHDY8OM0U/Qs8OTfIDWhUD4BLpNJUfXK4cGmjiagGze086m+mhI+V2nD0gfbHEnJjb9STA=="], "seroval-plugins": ["seroval-plugins@1.5.1", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-4FbuZ/TMl02sqv0RTFexu0SP6V+ywaIe5bAWCCEik0fk17BhALgwvUDVF7e3Uvf9pxmwCEJsRPmlkUE6HdzLAw=="], + "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], @@ -811,6 +962,8 @@ "split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="], + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + "stream-chain": ["stream-chain@2.2.5", "", {}, "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA=="], "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], @@ -853,6 +1006,8 @@ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -861,6 +1016,8 @@ "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + "typed-query-selector": ["typed-query-selector@2.12.1", "", {}, "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA=="], "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], @@ -869,6 +1026,8 @@ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], @@ -887,6 +1046,8 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], "vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.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" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="], @@ -895,6 +1056,8 @@ "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], @@ -919,6 +1082,8 @@ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + "zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -937,6 +1102,8 @@ "@tanstack/router-utils/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "c12/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], @@ -949,6 +1116,10 @@ "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "nypm/citty": ["citty@0.2.1", "", {}, "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg=="], @@ -961,14 +1132,20 @@ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.12", "", {}, "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw=="], + "send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "tsx/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "type-is/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], @@ -983,8 +1160,16 @@ "@scalar/themes/@scalar/types/nanoid": ["nanoid@5.1.9", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw=="], + "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "c12/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "@kitajs/ts-html-plugin/yargs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "@kitajs/ts-html-plugin/yargs/cliui/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], diff --git a/compose.yml b/compose.yml index aaaca7d..43b44b7 100644 --- a/compose.yml +++ b/compose.yml @@ -4,17 +4,30 @@ services: container_name: monitoring-app-stg restart: unless-stopped environment: + # App + - PORT=${PORT:-3000} + - NODE_ENV=${NODE_ENV:-production} + - BUN_PUBLIC_BASE_URL=${BUN_PUBLIC_BASE_URL} # Database - DATABASE_URL=${DATABASE_URL} - DIRECT_URL=${DIRECT_URL} # Google OAuth - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} - # App - - PORT=${PORT:-3000} - - NODE_ENV=${NODE_ENV:-production} - # Admin (initial Super Admin emails, comma-separated) + # Super Admin - SUPER_ADMIN_EMAIL=${SUPER_ADMIN_EMAIL} + # API Key + - API_KEY=${API_KEY} + # MinIO (object storage) + - MINIO_ENDPOINT=${MINIO_ENDPOINT} + - MINIO_PORT=${MINIO_PORT:-443} + - MINIO_USE_SSL=${MINIO_USE_SSL:-true} + - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY} + - MINIO_SECRET_KEY=${MINIO_SECRET_KEY} + - MINIO_BUCKET=${MINIO_BUCKET} + - MINIO_UPLOAD_DIR=${MINIO_UPLOAD_DIR:-bug-reports} + # Redis (optional — app logs feature) + - REDIS_URL=${REDIS_URL:-} networks: - public-net - postgres-net-stg diff --git a/package.json b/package.json index eb220c5..807f277 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "bun-react-template", - "version": "0.1.0", + "version": "0.1.1", "private": true, "type": "module", "scripts": { + "claude": "set -a && source .env && set +a && claude", "dev": "bun --watch src/serve.ts", "build": "vite build", "start": "NODE_ENV=production bun src/index.tsx", @@ -28,11 +29,17 @@ "@elysiajs/swagger": "^1.3.1", "@mantine/charts": "^9.0.0", "@mantine/core": "^8.3.18", + "@mantine/dates": "^9.1.1", "@mantine/hooks": "^8.3.18", + "@mantine/modals": "^8.3.18", "@mantine/notifications": "^8.3.18", + "@modelcontextprotocol/sdk": "^1.29.0", "@prisma/client": "6", "@tanstack/react-query": "^5.95.2", "@tanstack/react-router": "^1.168.10", + "@xyflow/react": "^12.6.4", + "dayjs": "^1.11.20", + "elkjs": "^0.9.3", "elysia": "^1.4.28", "minio": "^8.0.7", "postcss": "^8.5.8", @@ -42,10 +49,7 @@ "react-dom": "^19", "react-icons": "^5.6.0", "recharts": "^3.8.1", - "swr": "^2.4.1", - "@mantine/modals": "^8.3.18", - "@xyflow/react": "^12.6.4", - "elkjs": "^0.9.3" + "swr": "^2.4.1" }, "devDependencies": { "@biomejs/biome": "^2.4.10", diff --git a/prisma/migrations/20260429074454_add_app_config/migration.sql b/prisma/migrations/20260429074454_add_app_config/migration.sql new file mode 100644 index 0000000..5f1770b --- /dev/null +++ b/prisma/migrations/20260429074454_add_app_config/migration.sql @@ -0,0 +1,8 @@ +-- CreateTable +CREATE TABLE "app_config" ( + "key" TEXT NOT NULL, + "value" TEXT NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "app_config_pkey" PRIMARY KEY ("key") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 475e633..feb3cab 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -145,6 +145,14 @@ model BugLog { @@map("bug_log") } + +model AppConfig { + key String @id + value String + updatedAt DateTime @updatedAt + + @@map("app_config") +} diff --git a/scripts/mcp-deploy.ts b/scripts/mcp-deploy.ts new file mode 100644 index 0000000..5b9c4f0 --- /dev/null +++ b/scripts/mcp-deploy.ts @@ -0,0 +1,231 @@ +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' +import { z } from 'zod' + +const GH_TOKEN = process.env.GH_TOKEN ?? '' +const STACK_NAME = process.env.STACK_NAME ?? '' +const BASE_URL = process.env.BASE_URL ?? '' // https://api.github.com/repos/owner/repo +const STG_URL = process.env.STG_URL ?? '' // https://monitoring-stg.example.com +const VERSION_PATH = process.env.VERSION_PATH ?? '/api/system/version' + +// ─── GitHub API helpers ──────────────────────────────────────────────────────── + +const ghHeaders = { + Authorization: `Bearer ${GH_TOKEN}`, + Accept: 'application/vnd.github+json', + 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28', +} + +async function triggerWorkflow(workflow: string, inputs: Record) { + const res = await fetch(`${BASE_URL}/actions/workflows/${workflow}/dispatches`, { + method: 'POST', + headers: ghHeaders, + body: JSON.stringify({ ref: 'main', inputs }), + }) + if (!res.ok) throw new Error(`GitHub API error ${res.status}: ${await res.text()}`) +} + +async function waitForWorkflow( + workflow: string, + afterTime: Date, + timeoutMs = 600_000, +): Promise<{ conclusion: string; url: string }> { + const deadline = Date.now() + timeoutMs + await Bun.sleep(8_000) // tunggu run muncul di API + + while (Date.now() < deadline) { + const res = await fetch(`${BASE_URL}/actions/workflows/${workflow}/runs?per_page=5`, { + headers: ghHeaders, + }) + const data = await res.json() as { workflow_runs: any[] } + const run = data.workflow_runs?.find( + (r: any) => new Date(r.created_at) >= afterTime, + ) + + if (run) { + if (run.status === 'completed') { + return { conclusion: run.conclusion ?? 'failure', url: run.html_url } + } + } + + await Bun.sleep(12_000) + } + + throw new Error(`Workflow ${workflow} timeout setelah ${timeoutMs / 1000}s`) +} + +// ─── Shell helpers ───────────────────────────────────────────────────────────── + +async function sh(cmd: string[]): Promise<{ out: string; err: string; ok: boolean }> { + const proc = Bun.spawn(cmd, { stdout: 'pipe', stderr: 'pipe', cwd: process.cwd() }) + const [out, err, code] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]) + return { out: out.trim(), err: err.trim(), ok: code === 0 } +} + +// ─── MCP Server ──────────────────────────────────────────────────────────────── + +const server = new McpServer({ name: 'deploy-stg', version: '1.0.0' }) + +// ─── Tool: publish (manual, single step) ────────────────────────────────────── + +server.tool( + 'publish', + 'Trigger publish.yml untuk build & push Docker image staging', + { tag: z.string().describe('Image tag, contoh: 1.0.0') }, + async ({ tag }) => { + await triggerWorkflow('publish.yml', { stack_env: 'stg', tag }) + return { content: [{ type: 'text', text: `✅ publish.yml dipicu → stg-${tag}` }] } + }, +) + +// ─── Tool: repull (manual, single step) ─────────────────────────────────────── + +server.tool( + 'repull', + 'Trigger re-pull.yml untuk redeploy stack staging di Portainer', + {}, + async () => { + await triggerWorkflow('re-pull.yml', { stack_name: STACK_NAME, stack_env: 'stg' }) + return { content: [{ type: 'text', text: `✅ re-pull.yml dipicu → ${STACK_NAME}-stg` }] } + }, +) + +// ─── Tool: deploy (full pipeline) ───────────────────────────────────────────── + +server.tool( + 'deploy', + [ + 'Full deploy pipeline ke staging:', + '1. Cek pending migrations', + '2. Version bump di package.json', + '3. Commit & push ke build/stg', + '4. Trigger publish.yml → tunggu selesai', + '5. Trigger re-pull.yml → tunggu selesai', + '6. Cek version di staging & local untuk konfirmasi', + ].join('\n'), + { tag: z.string().describe('Versi baru, contoh: 1.2.3') }, + async ({ tag }) => { + const log: string[] = [] + + // ── 1. Cek & jalankan migrasi jika ada ───────────────────────────────── + const migrateStatus = await sh(['bunx', 'prisma', 'migrate', 'status']) + if (!migrateStatus.ok || migrateStatus.out.includes('not yet been applied')) { + log.push('⏳ Ada pending migrations — menjalankan migrate deploy...') + const migrateRun = await sh(['bunx', 'prisma', 'migrate', 'deploy']) + if (!migrateRun.ok) { + return { + content: [{ + type: 'text', + text: [ + ...log, + '❌ Migrate deploy gagal:', + migrateRun.err || migrateRun.out, + ].join('\n'), + }], + } + } + log.push('✅ Migrations: deployed') + } else { + log.push('✅ Migrations: up to date') + } + + // ── 2. Version bump ────────────────────────────────────────────────────── + const pkgPath = `${process.cwd()}/package.json` + const pkg = await Bun.file(pkgPath).json() + const prevVersion = pkg.version as string + pkg.version = tag + await Bun.write(pkgPath, JSON.stringify(pkg, null, 2) + '\n') + log.push(`✅ Version bump: ${prevVersion} → ${tag}`) + + // ── 3. Commit & push build/stg ─────────────────────────────────────────── + await sh(['git', 'add', 'package.json']) + const commit = await sh(['git', 'commit', '-m', `chore: bump version to ${tag}`]) + if (!commit.ok) { + return { content: [{ type: 'text', text: `❌ git commit gagal:\n${commit.err}` }] } + } + log.push('✅ Committed') + + const push = await sh(['git', 'push', 'origin', 'HEAD:build/stg']) + if (!push.ok) { + return { content: [{ type: 'text', text: `❌ git push gagal:\n${push.err}` }] } + } + log.push('✅ Pushed → build/stg') + + // ── 4. Publish workflow ────────────────────────────────────────────────── + log.push('⏳ Menjalankan publish.yml...') + const publishTriggeredAt = new Date() + await triggerWorkflow('publish.yml', { stack_env: 'stg', tag }) + + const publish = await waitForWorkflow('publish.yml', publishTriggeredAt) + if (publish.conclusion !== 'success') { + return { + content: [{ + type: 'text', + text: [ + ...log, + `❌ publish.yml ${publish.conclusion}`, + `Detail: ${publish.url}`, + ].join('\n'), + }], + } + } + log.push(`✅ publish.yml sukses → ${publish.url}`) + + // ── 5. Re-pull workflow ────────────────────────────────────────────────── + log.push('⏳ Menjalankan re-pull.yml...') + const repullTriggeredAt = new Date() + await triggerWorkflow('re-pull.yml', { stack_name: STACK_NAME, stack_env: 'stg' }) + + const repull = await waitForWorkflow('re-pull.yml', repullTriggeredAt) + if (repull.conclusion !== 'success') { + return { + content: [{ + type: 'text', + text: [ + ...log, + `❌ re-pull.yml ${repull.conclusion}`, + `Detail: ${repull.url}`, + ].join('\n'), + }], + } + } + log.push(`✅ re-pull.yml sukses → ${repull.url}`) + + // ── 6. Cek version ─────────────────────────────────────────────────────── + await Bun.sleep(5_000) // tunggu container restart + log.push('⏳ Mengecek version di staging...') + + const localCommitProc = await sh(['git', 'rev-parse', '--short', 'HEAD']) + const localCommit = localCommitProc.out + + let stgInfo: { version?: string; commit?: string } = {} + try { + const versionRes = await fetch(`${STG_URL}${VERSION_PATH}`) + stgInfo = await versionRes.json() + } catch (e) { + log.push(`⚠️ Gagal mengecek version staging: ${e}`) + } + + const versionMatch = stgInfo.version === tag + const commitMatch = stgInfo.commit === localCommit + + log.push('') + log.push('─── Version Check ───────────────────────────') + log.push(`Local : version=${tag}, commit=${localCommit}`) + log.push(`Staging: version=${stgInfo.version ?? '?'}, commit=${stgInfo.commit ?? '?'}`) + log.push(versionMatch && commitMatch + ? '✅ Staging sudah terupdate dan sesuai local' + : `⚠️ Mismatch — version: ${versionMatch ? 'OK' : 'BEDA'}, commit: ${commitMatch ? 'OK' : 'BEDA'}`, + ) + + return { content: [{ type: 'text', text: log.join('\n') }] } + }, +) + +const transport = new StdioServerTransport() +await server.connect(transport) diff --git a/src/app.ts b/src/app.ts index 411693c..8401f78 100644 --- a/src/app.ts +++ b/src/app.ts @@ -203,6 +203,10 @@ export function createApp() { }) } + if (!user.active) { + return new Response(null, { status: 302, headers: { Location: '/login?error=account_disabled' } }) + } + if (env.SUPER_ADMIN_EMAILS.includes(user.email) && user.role !== 'DEVELOPER') { user = await prisma.user.update({ where: { id: user.id }, data: { role: 'DEVELOPER' } }) } @@ -233,6 +237,10 @@ export function createApp() { set.status = 401 return { error: 'Email atau password salah' } } + if (!user.active) { + set.status = 403 + return { error: 'Akun Anda telah dinonaktifkan. Hubungi admin untuk informasi lebih lanjut.' } + } // Auto-promote super admin from env if (env.SUPER_ADMIN_EMAILS.includes(user.email) && user.role !== 'DEVELOPER') { user = await prisma.user.update({ where: { id: user.id }, data: { role: 'DEVELOPER' } }) @@ -281,13 +289,18 @@ export function createApp() { if (!token) { set.status = 401; return { user: null } } const session = await prisma.session.findUnique({ where: { token }, - include: { user: { select: { id: true, name: true, email: true, role: true, image: true } } }, + include: { user: { select: { id: true, name: true, email: true, role: true, image: true, active: true } } }, }) if (!session || session.expiresAt < new Date()) { if (session) await prisma.session.delete({ where: { id: session.id } }) set.status = 401 return { user: null } } + if (!session.user.active) { + await prisma.session.deleteMany({ where: { userId: session.user.id } }) + set.status = 401 + return { user: null } + } return { user: session.user } }, { detail: { @@ -414,6 +427,8 @@ export function createApp() { const search = query.search || '' const type = query.type as any const userId = query.userId + const dateFrom = query.dateFrom + const dateTo = query.dateTo const where: any = {} if (search) { @@ -428,6 +443,15 @@ export function createApp() { if (userId && userId !== 'all') { where.userId = userId } + if (dateFrom || dateTo) { + where.createdAt = {} + if (dateFrom) where.createdAt.gte = new Date(dateFrom) + if (dateTo) { + const end = new Date(dateTo) + end.setHours(23, 59, 59, 999) + where.createdAt.lte = end + } + } const [logs, total] = await Promise.all([ prisma.log.findMany({ @@ -452,6 +476,8 @@ export function createApp() { search: t.Optional(t.String({ description: 'Cari berdasarkan pesan log atau nama pengguna' })), type: t.Optional(t.String({ description: 'Filter tipe: CREATE | UPDATE | DELETE | LOGIN | LOGOUT | all' })), userId: t.Optional(t.String({ description: 'Filter berdasarkan ID pengguna, atau "all"' })), + dateFrom: t.Optional(t.String({ description: 'Filter dari tanggal (ISO string atau YYYY-MM-DD)' })), + dateTo: t.Optional(t.String({ description: 'Filter sampai tanggal (ISO string atau YYYY-MM-DD)' })), }), detail: { summary: 'List Activity Logs', @@ -628,6 +654,10 @@ export function createApp() { }, }) + if (body.active === false) { + await prisma.session.deleteMany({ where: { userId: id } }) + } + if (userId) { await createSystemLog(userId, 'UPDATE', `Updated user: ${user.name} (${user.email})`) } @@ -980,6 +1010,39 @@ export function createApp() { tags: ['System'], }, }) + .get('/api/system/version', async () => { + const pkg = await Bun.file('./package.json').json() + let commit = 'unknown' + let branch = 'unknown' + let changelog: { hash: string; date: string; author: string; message: string }[] = [] + try { + const commitProc = Bun.spawn(['git', 'rev-parse', '--short', 'HEAD'], { stdout: 'pipe', stderr: 'pipe' }) + commit = (await new Response(commitProc.stdout).text()).trim() + const branchProc = Bun.spawn(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], { stdout: 'pipe', stderr: 'pipe' }) + branch = (await new Response(branchProc.stdout).text()).trim() + const logProc = Bun.spawn( + ['git', 'log', '--pretty=format:%h|%aI|%an|%s', '-20'], + { stdout: 'pipe', stderr: 'pipe' }, + ) + const logText = (await new Response(logProc.stdout).text()).trim() + changelog = logText.split('\n').filter(Boolean).map(line => { + const [hash, date, author, ...msgParts] = line.split('|') + return { hash, date, author, message: msgParts.join('|') } + }) + } catch { /* git not available */ } + return { + version: pkg.version as string, + commit, + branch, + changelog, + } + }, { + detail: { + summary: 'Version Info', + description: 'Mengembalikan versi aplikasi, git commit hash, branch aktif, dan 20 commit terakhir sebagai changelog.', + tags: ['System'], + }, + }) // ─── Example API ─────────────────────────────────── .get('/api/hello', () => ({ @@ -1041,6 +1104,7 @@ export function createApp() { select: { id: true, name: true, email: true, role: true, active: true, createdAt: true }, }) await appLog('info', `Role changed: ${user.email} ${target?.role} → ${role}`) + await createSystemLog(auth.userId, 'UPDATE', `Role changed: ${user.name} (${user.email}) ${target?.role} → ${role}`) return { user } }) @@ -1056,6 +1120,7 @@ export function createApp() { }) if (!active) await prisma.session.deleteMany({ where: { userId: params.id } }) await appLog('info', `User ${active ? 'activated' : 'deactivated'}: ${user.email}`) + await createSystemLog(auth.userId, active ? 'UPDATE' : 'DELETE', `User ${active ? 'activated' : 'deactivated'}: ${user.name} (${user.email})`) return { user } }) @@ -1487,4 +1552,58 @@ export function createApp() { } return { sessions: result, summary: { totalSessions: result.length, activeSessions: active, expiredSessions: expired, onlineUsers: onlineIds.size, byRole } } }) + + // ─── App Config ──────────────────────────────────────────────────────────── + + .get('/api/admin/config', async ({ request, set }) => { + const auth = await requireDeveloper(request, set) + if (!auth) return { error: set.status === 401 ? 'Unauthorized' : 'Forbidden' } + const configs = await prisma.appConfig.findMany({ orderBy: { key: 'asc' } }) + return { configs: configs.map((c) => ({ key: c.key, value: c.value, updatedAt: c.updatedAt.toISOString() })) } + }, { + detail: { summary: 'Get App Config', tags: ['Admin'] }, + }) + + .put('/api/admin/config', async ({ request, set }) => { + const auth = await requireDeveloper(request, set) + if (!auth) return { error: set.status === 401 ? 'Unauthorized' : 'Forbidden' } + const body = await request.json() as { key: string; value: string } + if (!body.key || typeof body.value !== 'string') { set.status = 400; return { error: 'key and value required' } } + const config = await prisma.appConfig.upsert({ + where: { key: body.key }, + update: { value: body.value }, + create: { key: body.key, value: body.value }, + }) + await createSystemLog(auth.userId, 'UPDATE', `Updated app config: ${body.key}`) + return { key: config.key, value: config.value, updatedAt: config.updatedAt.toISOString() } + }, { + detail: { summary: 'Update App Config', tags: ['Admin'] }, + }) + + // ─── Desa Plus Proxy ─────────────────────────────────────────────────────── + + .all('/api/proxy/desa-plus/*', async ({ request, set }) => { + const [baseConfig, apiKeyConfig] = await Promise.all([ + prisma.appConfig.findUnique({ where: { key: 'URL_API_DESA_PLUS' } }), + prisma.appConfig.findUnique({ where: { key: 'API_KEY_DESA_PLUS' } }), + ]) + if (!baseConfig?.value) { set.status = 503; return { error: 'URL_API_DESA_PLUS belum dikonfigurasi. Set di /dev → Settings.' } } + const base = baseConfig.value.replace(/\/$/, '') + const url = new URL(request.url) + const upstream = `${base}${url.pathname.replace('/api/proxy/desa-plus', '')}${url.search}` + const headers = new Headers(request.headers) + headers.delete('host') + if (apiKeyConfig?.value) headers.set('X-API-Key', apiKeyConfig.value) + try { + const res = await fetch(upstream, { method: request.method, headers, body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined }) + const contentType = res.headers.get('content-type') ?? 'application/json' + set.status = res.status + return new Response(res.body, { status: res.status, headers: { 'content-type': contentType } }) + } catch (e) { + set.status = 502 + return { error: 'Gagal menghubungi API desa-plus', detail: String(e) } + } + }, { + detail: { summary: 'Proxy Desa Plus API', tags: ['Proxy'] }, + }) } diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx index 3648078..7f8c651 100644 --- a/src/frontend/App.tsx +++ b/src/frontend/App.tsx @@ -1,5 +1,6 @@ import { ColorSchemeScript, MantineProvider, createTheme } from '@mantine/core' import '@mantine/core/styles.css' +import '@mantine/dates/styles.css' import '@mantine/notifications/styles.css' import { ModalsProvider } from '@mantine/modals' import { Notifications } from '@mantine/notifications' diff --git a/src/frontend/components/DashboardLayout.tsx b/src/frontend/components/DashboardLayout.tsx index dfb3293..9470c48 100644 --- a/src/frontend/components/DashboardLayout.tsx +++ b/src/frontend/components/DashboardLayout.tsx @@ -201,12 +201,6 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { > Profile - } - onClick={() => navigate({ to: '/dashboard' })} - > - Settings - Danger Zone - `${API_BASE_URL}/api/monitoring/get-villages?page=${page}&search=${encodeURIComponent(search)}`, - infoVillages: (id: string) => - `${API_BASE_URL}/api/monitoring/info-villages?id=${id}`, - gridVillages: (id: string) => - `${API_BASE_URL}/api/monitoring/grid-villages?id=${id}`, - graphLogVillages: (id: string, time: string) => - `${API_BASE_URL}/api/monitoring/graph-log-villages?id=${id}&time=${time}`, - getUsers: (page: number, search: string) => - `${API_BASE_URL}/api/monitoring/user?page=${page}&search=${encodeURIComponent(search)}`, - getLogsAllVillages: (page: number, search: string) => - `${API_BASE_URL}/api/monitoring/log-all-villages?page=${page}&search=${encodeURIComponent(search)}`, - getGridOverview: () => `${API_BASE_URL}/api/monitoring/grid-overview`, - getDailyActivity: () => `${API_BASE_URL}/api/monitoring/daily-activity`, - getComparisonActivity: () => `${API_BASE_URL}/api/monitoring/comparison-activity`, - postVersionUpdate: () => `${API_BASE_URL}/api/monitoring/version-update`, - createVillages: () => `${API_BASE_URL}/api/monitoring/create-villages`, - createUser: () => `${API_BASE_URL}/api/monitoring/create-user`, - listRole: () => `${API_BASE_URL}/api/monitoring/list-userrole-villages`, - listGroup: (id: string) => `${API_BASE_URL}/api/monitoring/list-group-villages?id=${id}`, - listPosition: (id: string) => `${API_BASE_URL}/api/monitoring/list-position-villages?id=${id}`, - editUser: () => `${API_BASE_URL}/api/monitoring/edit-user`, - updateStatusVillages: () => `${API_BASE_URL}/api/monitoring/update-status-villages`, - editVillages: () => `${API_BASE_URL}/api/monitoring/edit-villages`, - getGlobalLogs: (page: number, search: string, type: string, userId: string) => - `/api/logs?page=${page}&search=${encodeURIComponent(search)}&type=${type}&userId=${userId}`, + getVillages: (page: number, search: string) => + `${DESA_PLUS_PROXY}/api/monitoring/get-villages?page=${page}&search=${encodeURIComponent(search)}`, + infoVillages: (id: string) => + `${DESA_PLUS_PROXY}/api/monitoring/info-villages?id=${id}`, + gridVillages: (id: string) => + `${DESA_PLUS_PROXY}/api/monitoring/grid-villages?id=${id}`, + graphLogVillages: (id: string, time: string) => + `${DESA_PLUS_PROXY}/api/monitoring/graph-log-villages?id=${id}&time=${time}`, + getUsers: (page: number, search: string) => + `${DESA_PLUS_PROXY}/api/monitoring/user?page=${page}&search=${encodeURIComponent(search)}`, + getLogsAllVillages: (page: number, search: string) => + `${DESA_PLUS_PROXY}/api/monitoring/log-all-villages?page=${page}&search=${encodeURIComponent(search)}`, + getGridOverview: () => `${DESA_PLUS_PROXY}/api/monitoring/grid-overview`, + getDailyActivity: () => `${DESA_PLUS_PROXY}/api/monitoring/daily-activity`, + getComparisonActivity: () => `${DESA_PLUS_PROXY}/api/monitoring/comparison-activity`, + postVersionUpdate: () => `${DESA_PLUS_PROXY}/api/monitoring/version-update`, + createVillages: () => `${DESA_PLUS_PROXY}/api/monitoring/create-villages`, + createUser: () => `${DESA_PLUS_PROXY}/api/monitoring/create-user`, + listRole: () => `${DESA_PLUS_PROXY}/api/monitoring/list-userrole-villages`, + listGroup: (id: string) => `${DESA_PLUS_PROXY}/api/monitoring/list-group-villages?id=${id}`, + listPosition: (id: string) => `${DESA_PLUS_PROXY}/api/monitoring/list-position-villages?id=${id}`, + editUser: () => `${DESA_PLUS_PROXY}/api/monitoring/edit-user`, + updateStatusVillages: () => `${DESA_PLUS_PROXY}/api/monitoring/update-status-villages`, + editVillages: () => `${DESA_PLUS_PROXY}/api/monitoring/edit-villages`, + getGlobalLogs: (page: number, search: string, type: string, userId: string, dateFrom?: string, dateTo?: string) => { + const params = new URLSearchParams({ page: String(page), search, type, userId }) + if (dateFrom) params.set('dateFrom', dateFrom) + if (dateTo) params.set('dateTo', dateTo) + return `/api/logs?${params}` + }, getLogOperators: () => `/api/logs/operators`, getOperators: (page: number, search: string) => `/api/operators?page=${page}&search=${encodeURIComponent(search)}`, diff --git a/src/frontend/routes/dev.tsx b/src/frontend/routes/dev.tsx index 4628dbb..e4e2ccf 100644 --- a/src/frontend/routes/dev.tsx +++ b/src/frontend/routes/dev.tsx @@ -70,6 +70,7 @@ import { TbUserSearch, TbUsers, } from 'react-icons/tb' +import { notifications } from '@mantine/notifications' import { type Role, useLogout, useSession } from '@/frontend/hooks/useAuth' import { usePresence } from '@/frontend/hooks/usePresence' @@ -109,8 +110,8 @@ const navItems = [ { label: 'Overview', icon: TbLayoutDashboard, key: 'overview' }, { label: 'Operators', icon: TbUsers, key: 'operators' }, { label: 'Bugs', icon: TbBug, key: 'bugs' }, - { label: 'App Logs', icon: TbServer, key: 'app-logs' }, - { label: 'Activity Logs', icon: TbActivity, key: 'activity-logs' }, + { label: 'App Logs', icon: TbServer, key: 'app-logs', disabled: true }, + // { label: 'Activity Logs', icon: TbActivity, key: 'activity-logs' }, { label: 'Database', icon: TbDatabase, key: 'database' }, { label: 'Project', icon: TbSitemap, key: 'project' }, { label: 'Settings', icon: TbSettings, key: 'settings' }, @@ -204,7 +205,8 @@ function DevPage() { variant={active === item.key ? 'filled' : 'subtle'} color={active === item.key ? 'blue' : 'gray'} size="lg" - onClick={() => setActive(item.key)} + disabled={item.disabled} + onClick={() => !item.disabled && setActive(item.key)} > @@ -218,7 +220,8 @@ function DevPage() { leftSection={} rightSection={active === item.key ? : undefined} active={active === item.key} - onClick={() => setActive(item.key)} + disabled={item.disabled} + onClick={() => !item.disabled && setActive(item.key)} style={{ borderRadius: 6 }} /> ) @@ -1459,15 +1462,130 @@ function StaticFlowPanel({ graph, flowKey }: { graph: { nodes: Node[]; edges: Ed // ─── Settings Panel ──────────────────────────────────────────────────────────── +interface AppConfigEntry { key: string; value: string; updatedAt: string } + +const CONFIG_DEFINITIONS: { key: string; label: string; description: string; placeholder: string; secret?: boolean }[] = [ + { + key: 'URL_API_DESA_PLUS', + label: 'URL API Desa Plus', + description: 'Base URL untuk API eksternal Desa Plus. Semua request dari frontend akan diproxy melalui server ke URL ini.', + placeholder: 'https://api.desa-plus.example.com', + }, + { + key: 'API_KEY_DESA_PLUS', + label: 'API Key Desa Plus', + description: 'API key untuk autentikasi ke API Desa Plus. Dikirim otomatis sebagai header X-API-Key pada setiap request proxy.', + placeholder: 'your-secret-api-key', + secret: true, + }, +] + function SettingsPanel() { + const qc = useQueryClient() + const [values, setValues] = useState>({}) + const [saved, setSaved] = useState(false) + + const { data, isLoading } = useQuery({ + queryKey: ['admin', 'config'], + queryFn: () => fetch('/api/admin/config', { credentials: 'include' }).then((r) => r.json()), + }) + + const configs: AppConfigEntry[] = data?.configs ?? [] + + useEffect(() => { + const initial: Record = {} + for (const def of CONFIG_DEFINITIONS) { + const existing = configs.find((c) => c.key === def.key) + initial[def.key] = existing?.value ?? '' + } + setValues(initial) + }, [configs]) + + const saveAllMutation = useMutation({ + mutationFn: async (vals: Record) => { + await Promise.all( + CONFIG_DEFINITIONS.map((def) => + fetch('/api/admin/config', { + method: 'PUT', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ key: def.key, value: vals[def.key] ?? '' }), + }) + ) + ) + }, + onSuccess: () => { + qc.invalidateQueries({ queryKey: ['admin', 'config'] }) + setSaved(true) + setTimeout(() => setSaved(false), 2000) + notifications.show({ color: 'green', title: 'Tersimpan', message: 'Konfigurasi berhasil disimpan.' }) + }, + onError: () => { + notifications.show({ color: 'red', title: 'Gagal', message: 'Terjadi kesalahan saat menyimpan konfigurasi.' }) + }, + }) + + const hasUnconfigured = CONFIG_DEFINITIONS.some((def) => !configs.find((c) => c.key === def.key)) + return ( Settings - -
- Konfigurasi sistem akan ditampilkan di sini. -
-
+ Konfigurasi runtime — perubahan langsung berlaku tanpa rebuild atau redeploy. + + {isLoading ?
: ( + + + {CONFIG_DEFINITIONS.map((def) => { + const existing = configs.find((c) => c.key === def.key) + return ( + + +
+ {def.label} + {def.key} +
+ {existing + ? Diupdate {new Date(existing.updatedAt).toLocaleString('id-ID')} + : Belum dikonfigurasi + } +
+ {def.description} + setValues((prev) => ({ ...prev, [def.key]: e.target.value }))} + placeholder={def.placeholder} + /> +
+ ) + })} + + {hasUnconfigured && ( + Beberapa konfigurasi belum diisi — data tidak akan ter-load sampai disimpan. + )} + + + + +
+
+ )}
) } diff --git a/src/frontend/routes/login.tsx b/src/frontend/routes/login.tsx index 7432d4f..ff41ee9 100644 --- a/src/frontend/routes/login.tsx +++ b/src/frontend/routes/login.tsx @@ -66,6 +66,7 @@ function LoginPage() { invalid_state: 'Sesi OAuth tidak valid, silakan coba lagi.', token_failed: 'Gagal menukar token Google, silakan coba lagi.', userinfo_failed: 'Gagal mengambil info akun Google, silakan coba lagi.', + account_disabled: 'Akun Anda telah dinonaktifkan. Hubungi admin untuk informasi lebih lanjut.', }[searchError ?? ''] ?? 'Login dengan Google gagal, silakan coba lagi.' )} diff --git a/src/frontend/routes/logs.tsx b/src/frontend/routes/logs.tsx index d148033..d5075aa 100644 --- a/src/frontend/routes/logs.tsx +++ b/src/frontend/routes/logs.tsx @@ -1,22 +1,24 @@ import { + ActionIcon, Badge, + Center, Container, Group, - Stack, - Text, - Paper, - TextInput, - Select, - Avatar, - Box, - Divider, + Loader, Pagination, - Center, - Tooltip, + SegmentedControl, + Select, + Stack, + Table, + Text, + Title, } from '@mantine/core' -import { useState, useMemo, useEffect } from 'react' +import { DatePickerInput, type DatesRangeValue } from '@mantine/dates' +import dayjs from 'dayjs' +import 'dayjs/locale/id' +import { useMemo, useState } from 'react' import { createFileRoute } from '@tanstack/react-router' -import { TbSearch, TbClock, TbCheck, TbX } from 'react-icons/tb' +import { TbRefresh } from 'react-icons/tb' import { DashboardLayout } from '@/frontend/components/DashboardLayout' import useSWR from 'swr' import { API_URLS } from '../config/api' @@ -25,263 +27,144 @@ export const Route = createFileRoute('/logs')({ component: GlobalLogsPage, }) -const fetcher = (url: string) => fetch(url).then((res) => res.json()) +const fetcher = (url: string) => fetch(url, { credentials: 'include' }).then((r) => r.json()) -const typeConfig: Record = { - CREATE: { color: 'blue', icon: TbCheck }, - UPDATE: { color: 'teal', icon: TbCheck }, - DELETE: { color: 'red', icon: TbX }, - LOGIN: { color: 'green', icon: TbClock }, - LOGOUT: { color: 'orange', icon: TbClock }, -} - -const getRoleColor = (role: string) => { - const r = (role || '').toLowerCase() - if (r.includes('super')) return 'red' - if (r.includes('admin')) return 'brand-blue' - if (r.includes('developer')) return 'violet' - return 'gray' -} - -function groupLogsByDate(logs: any[]) { - const groups: Record = {} - - const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }).toUpperCase() - const yesterday = new Date(Date.now() - 86400000).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }).toUpperCase() - - logs.forEach(log => { - const dateObj = new Date(log.createdAt) - let dateStr = dateObj.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }).toUpperCase() - - if (dateStr === today) dateStr = 'TODAY' - else if (dateStr === yesterday) dateStr = 'YESTERDAY' - - if (!groups[dateStr]) groups[dateStr] = [] - - const timeStr = dateObj.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) - - groups[dateStr].push({ - id: log.id, - time: timeStr, - user: log.user, - type: log.type, - content: log.message, - color: log.user ? getRoleColor(log.user.role) : 'gray', - icon: typeConfig[log.type as string]?.icon - }) - }) - - // We want to keep the order as they came from the API (sorted by createdAt desc) - // but grouped by date. Object.entries might mess up the order if dates are not sequential. - // However, since the source logs are sorted, the first encounter of a date defines the group order. - const result: { date: string; logs: any[] }[] = [] - const seenDates = new Set() - - logs.forEach(log => { - const dateObj = new Date(log.createdAt) - let dateStr = dateObj.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }).toUpperCase() - if (dateStr === today) dateStr = 'TODAY' - else if (dateStr === yesterday) dateStr = 'YESTERDAY' - - if (!seenDates.has(dateStr)) { - result.push({ date: dateStr, logs: groups[dateStr] }) - seenDates.add(dateStr) - } - }) - - return result +const LOG_TYPES = ['all', 'LOGIN', 'LOGOUT', 'CREATE', 'UPDATE', 'DELETE'] as const +const LOG_TYPE_COLOR: Record = { + LOGIN: 'green', + LOGOUT: 'gray', + CREATE: 'blue', + UPDATE: 'yellow', + DELETE: 'red', } function GlobalLogsPage() { - const [search, setSearch] = useState('') - const [debouncedSearch, setDebouncedSearch] = useState('') - const [logType, setLogType] = useState('all') - const [operatorId, setOperatorId] = useState('all') + const [type, setType] = useState('all') + const [operatorId, setOperatorId] = useState('all') + const [dateRange, setDateRange] = useState([null, null]) const [page, setPage] = useState(1) - useEffect(() => { - const timer = setTimeout(() => setDebouncedSearch(search), 300) - return () => clearTimeout(timer) - }, [search]) - const { data: operatorsData } = useSWR(API_URLS.getLogOperators(), fetcher) const operatorOptions = useMemo(() => { - if (!operatorsData || !Array.isArray(operatorsData)) return [{ value: 'all', label: 'All Operators' }] + if (!operatorsData || !Array.isArray(operatorsData)) return [{ value: 'all', label: 'Semua operator' }] return [ - { value: 'all', label: 'All Operators' }, - ...operatorsData.map((op: any) => ({ value: op.id, label: op.name })) + { value: 'all', label: 'Semua user' }, + ...operatorsData.map((op: any) => ({ value: op.id, label: op.name })), ] }, [operatorsData]) - const { data: response, isLoading } = useSWR( - API_URLS.getGlobalLogs(page, debouncedSearch, logType || 'all', operatorId || 'all'), - fetcher + const dateFrom = dateRange[0] ? dayjs(dateRange[0]).format('YYYY-MM-DD') : undefined + const dateTo = dateRange[1] ? dayjs(dateRange[1]).format('YYYY-MM-DD') : undefined + + const { data, isLoading, mutate } = useSWR( + API_URLS.getGlobalLogs(page, '', type, operatorId, dateFrom, dateTo), + fetcher, + { refreshInterval: 10_000 }, ) - const filteredTimeline = useMemo(() => { - if (!response?.data) return [] - return groupLogsByDate(response.data) - }, [response?.data]) + const logs: any[] = data?.data ?? [] + const totalPages: number = data?.totalPages ?? 1 return ( - - {/* Header Controls */} - - } - radius="md" - w={250} - value={search} - onChange={(e) => { - setSearch(e.currentTarget.value) - setPage(1) - }} - /> - { - setOperatorId(val) - setPage(1) - }} - /> - + + + Activity Logs + mutate()}> + + + + + +