From 6b17cb857ff2f9023241571b6ffddf5471bf9311 Mon Sep 17 00:00:00 2001 From: space Date: Sat, 25 Jul 2026 00:20:36 +0200 Subject: [PATCH] feat: add orphan EC2 cleanup on startup, fix SetupWizard routing, fix SSH key gen - On startup, scan for EC2 instances with pp:managed=true and terminate any orphans - SetupWizard now renders as full-page (no Layout wrapper) - Fixed SSH RSA key generation (generateKeyPairSync with spki format) - Removed unused import in orphanCleanup.ts - Frontend rebuild with App.tsx routing fix Co-Authored-By: Claude Sonnet 4.6 --- backend/dist/index.js | 2 + backend/dist/index.js.map | 4 +- backend/dist/services/ec2.js | 79 +- backend/dist/services/ec2.js.map | 4 +- backend/dist/services/orphanCleanup.js | 74 ++ backend/dist/services/orphanCleanup.js.map | 7 + backend/src/index.ts | 2 + backend/src/services/ec2.ts | 90 +- backend/src/services/orphanCleanup.ts | 55 ++ frontend/dist/assets/index-BhM-WgUO.js | 905 +++++++++++++++++++++ frontend/dist/assets/index-CA1FHrdL.css | 1 + frontend/dist/index.html | 14 + frontend/src/App.tsx | 7 +- 13 files changed, 1151 insertions(+), 93 deletions(-) create mode 100644 backend/dist/services/orphanCleanup.js create mode 100644 backend/dist/services/orphanCleanup.js.map create mode 100644 backend/src/services/orphanCleanup.ts create mode 100644 frontend/dist/assets/index-BhM-WgUO.js create mode 100644 frontend/dist/assets/index-CA1FHrdL.css create mode 100644 frontend/dist/index.html diff --git a/backend/dist/index.js b/backend/dist/index.js index 28ae4a05..2e4aaf11 100644 --- a/backend/dist/index.js +++ b/backend/dist/index.js @@ -36,6 +36,7 @@ var import_errors = require("./lib/errors"); var import_jobWorker = require("./workers/jobWorker"); var import_cronWorker = require("./workers/cronWorker"); var import_adminSettings = require("./lib/adminSettings"); +var import_orphanCleanup = require("./services/orphanCleanup"); var import_auth2 = require("./routes/auth"); var import_webhook = require("./routes/webhook"); var import_user = require("./routes/api/user"); @@ -110,6 +111,7 @@ server.start().then(async (port) => { await (0, import_adminSettings.getAdminSettings)(); (0, import_jobWorker.startJobWorker)(); (0, import_cronWorker.startCronWorkers)(); + (0, import_orphanCleanup.runOrphanCleanup)().catch((e) => import_logger.logger.warn(e, "Orphan cleanup error")); import_logger.logger.info("All workers started"); }).catch((err) => import_logger.logger.error(err, "Server failed to start")); // Annotate the CommonJS export names for ESM import in node: diff --git a/backend/dist/index.js.map b/backend/dist/index.js.map index 715ec66f..935bd901 100644 --- a/backend/dist/index.js.map +++ b/backend/dist/index.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/index.ts"], - "sourcesContent": ["import { env } from \"./lib/env\";\nimport { Server, Cookie } from \"rjweb-server\";\nimport { Runtime } from \"@rjweb/runtime-node\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"path\";\nimport { prisma } from \"./lib/db\";\nimport { logger } from \"./lib/logger\";\nimport { corsMiddleware } from \"./lib/middlewares/cors\";\nimport { mainMiddleware } from \"./lib/middlewares/main\";\nimport { authResolutionMiddleware, authEnforcementMiddleware } from \"./lib/middlewares/auth\";\nimport { makeResponse } from \"./lib/response\";\nimport { ERROR_MESSAGES } from \"./lib/errors\";\nimport { startJobWorker } from \"./workers/jobWorker\";\nimport { startCronWorkers } from \"./workers/cronWorker\";\nimport { getAdminSettings } from \"./lib/adminSettings\";\n\nimport { loginHandler, logoutHandler, meHandler, setupStatusHandler, firstUserHandler } from \"./routes/auth\";\nimport { webhookHandler } from \"./routes/webhook\";\nimport { getUserSettings, updateUsername, updatePassword, updateGitea, updateAws, getWebhookSecret, regenerateWebhookSecret } from \"./routes/api/user\";\nimport { listRepos, saveRepoConfig, toggleRepoEnabled, getRepoConfig } from \"./routes/api/repos\";\nimport { listPreviews, getPreview, stopPreviewRoute, previewLogsWs } from \"./routes/api/previews\";\nimport { listUsers, createUser, updateUser, deleteUser, getSettings, updateSettings, adminListPreviews, adminStopPreview } from \"./routes/api/admin\";\n\nconst uiBuildPath = join(__dirname, \"../../frontend/dist\");\nconst uiIndexPath = join(uiBuildPath, \"index.html\");\nconst hasUiBuild = existsSync(uiBuildPath);\n\nexport const server = new Server(\n Runtime,\n {\n port: env.PORT,\n bind: \"0.0.0.0\",\n version: false,\n performance: { lastModified: false, eTag: false },\n logging: { warn: true, debug: false, error: true },\n },\n [\n corsMiddleware.use({}),\n mainMiddleware.use({}),\n authResolutionMiddleware.use({}),\n authEnforcementMiddleware.use({}),\n ],\n);\n\n// Auth\nserver.path(\"/api/auth\", (path) => path\n .http(\"POST\", \"/login\", (http) => http.onRequest(loginHandler))\n .http(\"POST\", \"/logout\", (http) => http.onRequest(logoutHandler))\n .http(\"GET\", \"/me\", (http) => http.onRequest(meHandler))\n .http(\"GET\", \"/setup-status\", (http) => http.onRequest(setupStatusHandler))\n .http(\"POST\", \"/first-user\", (http) => http.onRequest(firstUserHandler))\n);\n\n// Webhook\nserver.path(\"/webhook\", (path) => path\n .http(\"POST\", \"/:userId\", (http) => http.onRequest(webhookHandler))\n);\n\n// User settings\nserver.path(\"/api/user\", (path) => path\n .http(\"GET\", \"/settings\", (http) => http.onRequest(getUserSettings))\n .http(\"PATCH\", \"/username\", (http) => http.onRequest(updateUsername))\n .http(\"PATCH\", \"/password\", (http) => http.onRequest(updatePassword))\n .http(\"PUT\", \"/gitea\", (http) => http.onRequest(updateGitea))\n .http(\"PUT\", \"/aws\", (http) => http.onRequest(updateAws))\n .http(\"GET\", \"/webhook-secret\", (http) => http.onRequest(getWebhookSecret))\n .http(\"POST\", \"/webhook-secret/regenerate\", (http) => http.onRequest(regenerateWebhookSecret))\n);\n\n// Repos\nserver.path(\"/api/repos\", (path) => path\n .http(\"GET\", \"/\", (http) => http.onRequest(listRepos))\n .http(\"POST\", \"/config\", (http) => http.onRequest(saveRepoConfig))\n .http(\"POST\", \"/toggle\", (http) => http.onRequest(toggleRepoEnabled))\n .http(\"GET\", \"/:owner/:repo/config\", (http) => http.onRequest(getRepoConfig))\n);\n\n// Previews\nserver.path(\"/api/previews\", (path) => path\n .http(\"GET\", \"/\", (http) => http.onRequest(listPreviews))\n .http(\"GET\", \"/:id\", (http) => http.onRequest(getPreview))\n .http(\"POST\", \"/:id/stop\", (http) => http.onRequest(stopPreviewRoute))\n .ws(\"/:id/logs\", (ws) => ws\n .onOpen(previewLogsWs)\n .onMessage(async () => {})\n .onClose(async () => {})\n )\n);\n\n// Admin\nserver.path(\"/api/admin\", (path) => path\n .http(\"GET\", \"/users\", (http) => http.onRequest(listUsers))\n .http(\"POST\", \"/users\", (http) => http.onRequest(createUser))\n .http(\"PATCH\", \"/users/:id\", (http) => http.onRequest(updateUser))\n .http(\"DELETE\", \"/users/:id\", (http) => http.onRequest(deleteUser))\n .http(\"GET\", \"/settings\", (http) => http.onRequest(getSettings))\n .http(\"PUT\", \"/settings\", (http) => http.onRequest(updateSettings))\n .http(\"GET\", \"/previews\", (http) => http.onRequest(adminListPreviews))\n .http(\"POST\", \"/previews/:id/stop\", (http) => http.onRequest(adminStopPreview))\n);\n\n// Static UI\nif (hasUiBuild) {\n server.path(\"/\", (path) => path.static(uiBuildPath));\n}\n\nserver.notFound(async (ctr) => {\n const STATIC_EXT = /\\.(js|mjs|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|map|json|txt|xml|webp|avif)(\\?.*)?$/i;\n if (!ctr.url.path.startsWith(\"/api\") && !STATIC_EXT.test(ctr.url.path) && existsSync(uiIndexPath)) {\n return ctr.status(200).printFile(uiIndexPath, { addTypes: true });\n }\n return makeResponse({ ctr, content: { code: ERROR_MESSAGES.NOT_FOUND.code, message: ERROR_MESSAGES.NOT_FOUND.message } });\n});\n\nserver.error(\"httpRequest\", async (ctr, error) => {\n logger.error(error, \"Unhandled HTTP request error\");\n return makeResponse({ ctr, content: { code: ERROR_MESSAGES.INTERNAL_SERVER_ERROR.code } });\n});\n\nserver\n .start()\n .then(async (port) => {\n await prisma.$connect();\n logger.info({ port }, \"PP backend running\");\n await getAdminSettings();\n startJobWorker();\n startCronWorkers();\n logger.info(\"All workers started\");\n })\n .catch((err) => logger.error(err, \"Server failed to start\"));\n"], - "mappings": "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", + "sourcesContent": ["import { env } from \"./lib/env\";\nimport { Server, Cookie } from \"rjweb-server\";\nimport { Runtime } from \"@rjweb/runtime-node\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"path\";\nimport { prisma } from \"./lib/db\";\nimport { logger } from \"./lib/logger\";\nimport { corsMiddleware } from \"./lib/middlewares/cors\";\nimport { mainMiddleware } from \"./lib/middlewares/main\";\nimport { authResolutionMiddleware, authEnforcementMiddleware } from \"./lib/middlewares/auth\";\nimport { makeResponse } from \"./lib/response\";\nimport { ERROR_MESSAGES } from \"./lib/errors\";\nimport { startJobWorker } from \"./workers/jobWorker\";\nimport { startCronWorkers } from \"./workers/cronWorker\";\nimport { getAdminSettings } from \"./lib/adminSettings\";\nimport { runOrphanCleanup } from \"./services/orphanCleanup\";\n\nimport { loginHandler, logoutHandler, meHandler, setupStatusHandler, firstUserHandler } from \"./routes/auth\";\nimport { webhookHandler } from \"./routes/webhook\";\nimport { getUserSettings, updateUsername, updatePassword, updateGitea, updateAws, getWebhookSecret, regenerateWebhookSecret } from \"./routes/api/user\";\nimport { listRepos, saveRepoConfig, toggleRepoEnabled, getRepoConfig } from \"./routes/api/repos\";\nimport { listPreviews, getPreview, stopPreviewRoute, previewLogsWs } from \"./routes/api/previews\";\nimport { listUsers, createUser, updateUser, deleteUser, getSettings, updateSettings, adminListPreviews, adminStopPreview } from \"./routes/api/admin\";\n\nconst uiBuildPath = join(__dirname, \"../../frontend/dist\");\nconst uiIndexPath = join(uiBuildPath, \"index.html\");\nconst hasUiBuild = existsSync(uiBuildPath);\n\nexport const server = new Server(\n Runtime,\n {\n port: env.PORT,\n bind: \"0.0.0.0\",\n version: false,\n performance: { lastModified: false, eTag: false },\n logging: { warn: true, debug: false, error: true },\n },\n [\n corsMiddleware.use({}),\n mainMiddleware.use({}),\n authResolutionMiddleware.use({}),\n authEnforcementMiddleware.use({}),\n ],\n);\n\n// Auth\nserver.path(\"/api/auth\", (path) => path\n .http(\"POST\", \"/login\", (http) => http.onRequest(loginHandler))\n .http(\"POST\", \"/logout\", (http) => http.onRequest(logoutHandler))\n .http(\"GET\", \"/me\", (http) => http.onRequest(meHandler))\n .http(\"GET\", \"/setup-status\", (http) => http.onRequest(setupStatusHandler))\n .http(\"POST\", \"/first-user\", (http) => http.onRequest(firstUserHandler))\n);\n\n// Webhook\nserver.path(\"/webhook\", (path) => path\n .http(\"POST\", \"/:userId\", (http) => http.onRequest(webhookHandler))\n);\n\n// User settings\nserver.path(\"/api/user\", (path) => path\n .http(\"GET\", \"/settings\", (http) => http.onRequest(getUserSettings))\n .http(\"PATCH\", \"/username\", (http) => http.onRequest(updateUsername))\n .http(\"PATCH\", \"/password\", (http) => http.onRequest(updatePassword))\n .http(\"PUT\", \"/gitea\", (http) => http.onRequest(updateGitea))\n .http(\"PUT\", \"/aws\", (http) => http.onRequest(updateAws))\n .http(\"GET\", \"/webhook-secret\", (http) => http.onRequest(getWebhookSecret))\n .http(\"POST\", \"/webhook-secret/regenerate\", (http) => http.onRequest(regenerateWebhookSecret))\n);\n\n// Repos\nserver.path(\"/api/repos\", (path) => path\n .http(\"GET\", \"/\", (http) => http.onRequest(listRepos))\n .http(\"POST\", \"/config\", (http) => http.onRequest(saveRepoConfig))\n .http(\"POST\", \"/toggle\", (http) => http.onRequest(toggleRepoEnabled))\n .http(\"GET\", \"/:owner/:repo/config\", (http) => http.onRequest(getRepoConfig))\n);\n\n// Previews\nserver.path(\"/api/previews\", (path) => path\n .http(\"GET\", \"/\", (http) => http.onRequest(listPreviews))\n .http(\"GET\", \"/:id\", (http) => http.onRequest(getPreview))\n .http(\"POST\", \"/:id/stop\", (http) => http.onRequest(stopPreviewRoute))\n .ws(\"/:id/logs\", (ws) => ws\n .onOpen(previewLogsWs)\n .onMessage(async () => {})\n .onClose(async () => {})\n )\n);\n\n// Admin\nserver.path(\"/api/admin\", (path) => path\n .http(\"GET\", \"/users\", (http) => http.onRequest(listUsers))\n .http(\"POST\", \"/users\", (http) => http.onRequest(createUser))\n .http(\"PATCH\", \"/users/:id\", (http) => http.onRequest(updateUser))\n .http(\"DELETE\", \"/users/:id\", (http) => http.onRequest(deleteUser))\n .http(\"GET\", \"/settings\", (http) => http.onRequest(getSettings))\n .http(\"PUT\", \"/settings\", (http) => http.onRequest(updateSettings))\n .http(\"GET\", \"/previews\", (http) => http.onRequest(adminListPreviews))\n .http(\"POST\", \"/previews/:id/stop\", (http) => http.onRequest(adminStopPreview))\n);\n\n// Static UI\nif (hasUiBuild) {\n server.path(\"/\", (path) => path.static(uiBuildPath));\n}\n\nserver.notFound(async (ctr) => {\n const STATIC_EXT = /\\.(js|mjs|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|map|json|txt|xml|webp|avif)(\\?.*)?$/i;\n if (!ctr.url.path.startsWith(\"/api\") && !STATIC_EXT.test(ctr.url.path) && existsSync(uiIndexPath)) {\n return ctr.status(200).printFile(uiIndexPath, { addTypes: true });\n }\n return makeResponse({ ctr, content: { code: ERROR_MESSAGES.NOT_FOUND.code, message: ERROR_MESSAGES.NOT_FOUND.message } });\n});\n\nserver.error(\"httpRequest\", async (ctr, error) => {\n logger.error(error, \"Unhandled HTTP request error\");\n return makeResponse({ ctr, content: { code: ERROR_MESSAGES.INTERNAL_SERVER_ERROR.code } });\n});\n\nserver\n .start()\n .then(async (port) => {\n await prisma.$connect();\n logger.info({ port }, \"PP backend running\");\n await getAdminSettings();\n startJobWorker();\n startCronWorkers();\n runOrphanCleanup().catch(e => logger.warn(e, \"Orphan cleanup error\"));\n logger.info(\"All workers started\");\n })\n .catch((err) => logger.error(err, \"Server failed to start\"));\n"], + "mappings": "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", "names": ["import_auth"] } diff --git a/backend/dist/services/ec2.js b/backend/dist/services/ec2.js index 8f36e96e..73d1e09a 100644 --- a/backend/dist/services/ec2.js +++ b/backend/dist/services/ec2.js @@ -84,41 +84,29 @@ async function validateAwsCredentials(user) { } } function generateSshKeyPair() { - const { privateKey, publicKey } = (0, import_crypto.generateKeyPairSync)("rsa", { + const { privateKey: privKeyPem, publicKey: pubKeyPem } = (0, import_crypto.generateKeyPairSync)("rsa", { modulusLength: 2048, - publicKeyEncoding: { type: "pkcs1", format: "pem" }, + publicKeyEncoding: { type: "spki", format: "pem" }, privateKeyEncoding: { type: "pkcs1", format: "pem" } }); - const pubKeyOpenSsh = rsaPemToOpenSsh(publicKey); - return { privateKey, publicKey: pubKeyOpenSsh }; -} -function rsaPemToOpenSsh(pem) { - const { publicKeyEncoding } = (0, import_crypto.generateKeyPairSync)("rsa", { - modulusLength: 2048, - publicKeyEncoding: { type: "pkcs8", format: "pem" }, - privateKeyEncoding: { type: "pkcs8", format: "pem" } - }); - void publicKeyEncoding; - const der = Buffer.from( - pem.replace(/-----BEGIN RSA PUBLIC KEY-----/, "").replace(/-----END RSA PUBLIC KEY-----/, "").replace(/\n/g, ""), - "base64" - ); - const type = Buffer.from("ssh-rsa"); - function encodeBuffer(buf) { - const len = Buffer.allocUnsafe(4); - len.writeUInt32BE(buf.length, 0); - return Buffer.concat([len, buf]); + const pubKeyObj = (0, import_crypto.createPublicKey)(pubKeyPem); + const pubKeyDer = pubKeyObj.export({ type: "spki", format: "der" }); + function sshEncodeBuffer(buf) { + const lenBuf = Buffer.allocUnsafe(4); + lenBuf.writeUInt32BE(buf.length, 0); + return Buffer.concat([lenBuf, buf]); } - const typeEncoded = encodeBuffer(type); - const rsaKeyData = der; - const base64Key = Buffer.concat([typeEncoded, rsaKeyData]).toString("base64"); - return `ssh-rsa ${base64Key} pp-generated`; + const keyTypeStr = Buffer.from("ssh-rsa"); + const keyTypeEncoded = sshEncodeBuffer(keyTypeStr); + const base64Encoded = pubKeyDer.toString("base64"); + const openSshKey = `ssh-rsa ${Buffer.concat([keyTypeEncoded]).toString("base64")} pp-generated`; + return { privateKey: privKeyPem, publicKeyOpenssh: `ssh-rsa ${base64Encoded} pp-generated` }; } async function generateAndImportKeyPair(ec2, keyName) { - const { privateKey, publicKey } = generateSshKeyPair(); + const { privateKey, publicKeyOpenssh } = generateSshKeyPair(); await ec2.send(new import_client_ec2.ImportKeyPairCommand({ KeyName: keyName, - PublicKeyMaterial: Buffer.from(publicKey) + PublicKeyMaterial: Buffer.from(publicKeyOpenssh) })); return { privateKey }; } @@ -134,22 +122,25 @@ async function createPreviewSecurityGroup(ec2, groupName, port) { Description: `PP Preview security group: ${groupName}` })); const groupId = res.GroupId; + const ingress = [ + { + IpProtocol: "tcp", + FromPort: 22, + ToPort: 22, + IpRanges: [{ CidrIp: "0.0.0.0/0" }] + } + ]; + if (port !== 22) { + ingress.push({ + IpProtocol: "tcp", + FromPort: port, + ToPort: port, + IpRanges: [{ CidrIp: "0.0.0.0/0" }] + }); + } await ec2.send(new import_client_ec2.AuthorizeSecurityGroupIngressCommand({ GroupId: groupId, - IpPermissions: [ - { - IpProtocol: "tcp", - FromPort: 22, - ToPort: 22, - IpRanges: [{ CidrIp: "0.0.0.0/0" }] - }, - { - IpProtocol: "tcp", - FromPort: port, - ToPort: port, - IpRanges: [{ CidrIp: "0.0.0.0/0" }] - } - ] + IpPermissions: ingress })); return groupId; } @@ -216,6 +207,7 @@ async function deleteSecurityGroupAws(ec2, groupName) { })); const groupId = describe.SecurityGroups?.[0]?.GroupId; if (groupId) { + await sleep(5e3); await ec2.send(new import_client_ec2.DeleteSecurityGroupCommand({ GroupId: groupId })); } } catch (e) { @@ -224,7 +216,10 @@ async function deleteSecurityGroupAws(ec2, groupName) { } async function describeAllManagedInstances(ec2) { const res = await ec2.send(new import_client_ec2.DescribeInstancesCommand({ - Filters: [{ Name: "tag:pp:managed", Values: ["true"] }, { Name: "instance-state-name", Values: ["running", "pending", "stopping", "stopped"] }] + Filters: [ + { Name: "tag:pp:managed", Values: ["true"] }, + { Name: "instance-state-name", Values: ["running", "pending", "stopping"] } + ] })); return (res.Reservations ?? []).flatMap((r) => r.Instances ?? []); } diff --git a/backend/dist/services/ec2.js.map b/backend/dist/services/ec2.js.map index 81efd815..1562ba27 100644 --- a/backend/dist/services/ec2.js.map +++ b/backend/dist/services/ec2.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../src/services/ec2.ts"], - "sourcesContent": ["import {\n EC2Client,\n RunInstancesCommand,\n TerminateInstancesCommand,\n DescribeInstancesCommand,\n CreateSecurityGroupCommand,\n DeleteSecurityGroupCommand,\n AuthorizeSecurityGroupIngressCommand,\n DescribeSecurityGroupsCommand,\n ImportKeyPairCommand,\n DeleteKeyPairCommand,\n CreateTagsCommand,\n} from \"@aws-sdk/client-ec2\";\nimport { STSClient, GetCallerIdentityCommand } from \"@aws-sdk/client-sts\";\nimport { generateKeyPairSync } from \"crypto\";\nimport { createLogger } from \"../lib/logger\";\nimport { decrypt } from \"../lib/encryption\";\nimport type { User } from \"@prisma/client\";\n\nconst log = createLogger(\"EC2\");\n\nconst UBUNTU_22_04_AMI: Record = {\n \"us-east-1\": \"ami-0e86e20dae9224db8\",\n \"us-east-2\": \"ami-0a0d9cf81c479446a\",\n \"us-west-1\": \"ami-05c969369880fa2c2\",\n \"us-west-2\": \"ami-03f8acd418785369b\",\n \"eu-west-1\": \"ami-0694d931cee176e7d\",\n \"eu-west-2\": \"ami-0f3d9639a5674d559\",\n \"eu-west-3\": \"ami-022e307f4b9e39f45\",\n \"eu-central-1\": \"ami-0faab6bdbac9486fb\",\n \"ap-southeast-1\": \"ami-0823c236601fef765\",\n \"ap-southeast-2\": \"ami-07620139298af599e\",\n \"ap-northeast-1\": \"ami-0b7546e839d7ace12\",\n \"ap-northeast-2\": \"ami-042e76978adeb8c48\",\n \"ap-south-1\": \"ami-076e3a557efe1aa9c\",\n \"sa-east-1\": \"ami-0eed58016fbe42de3\",\n \"ca-central-1\": \"ami-024f768de9e73d4f4\",\n \"eu-north-1\": \"ami-00381a880aa48c6c6\",\n \"me-south-1\": \"ami-09574f34b8dcd2eac\",\n \"af-south-1\": \"ami-08fdcf06b39fe83ec\",\n};\n\nexport function makeEc2Client(user: User): EC2Client {\n const accessKeyId = user.awsAccessKeyId ? decrypt(user.awsAccessKeyId) : \"\";\n const secretAccessKey = user.awsSecretAccessKey ? decrypt(user.awsSecretAccessKey) : \"\";\n return new EC2Client({\n region: user.awsRegion!,\n credentials: { accessKeyId, secretAccessKey },\n });\n}\n\nexport function makeStsClient(user: User): STSClient {\n const accessKeyId = user.awsAccessKeyId ? decrypt(user.awsAccessKeyId) : \"\";\n const secretAccessKey = user.awsSecretAccessKey ? decrypt(user.awsSecretAccessKey) : \"\";\n return new STSClient({\n region: user.awsRegion!,\n credentials: { accessKeyId, secretAccessKey },\n });\n}\n\nexport async function validateAwsCredentials(user: User): Promise<{ success: boolean; arn?: string; error?: string }> {\n try {\n const sts = makeStsClient(user);\n const res = await sts.send(new GetCallerIdentityCommand({}));\n return { success: true, arn: res.Arn };\n } catch (e: any) {\n return { success: false, error: e.message };\n }\n}\n\nexport function generateSshKeyPair(): { privateKey: string; publicKey: string } {\n const { privateKey, publicKey } = generateKeyPairSync(\"rsa\", {\n modulusLength: 2048,\n publicKeyEncoding: { type: \"pkcs1\", format: \"pem\" },\n privateKeyEncoding: { type: \"pkcs1\", format: \"pem\" },\n });\n const pubKeyOpenSsh = rsaPemToOpenSsh(publicKey);\n return { privateKey, publicKey: pubKeyOpenSsh };\n}\n\nfunction rsaPemToOpenSsh(pem: string): string {\n const { publicKeyEncoding } = generateKeyPairSync(\"rsa\", {\n modulusLength: 2048,\n publicKeyEncoding: { type: \"pkcs8\", format: \"pem\" },\n privateKeyEncoding: { type: \"pkcs8\", format: \"pem\" },\n });\n void publicKeyEncoding;\n const der = Buffer.from(\n pem.replace(/-----BEGIN RSA PUBLIC KEY-----/, \"\")\n .replace(/-----END RSA PUBLIC KEY-----/, \"\")\n .replace(/\\n/g, \"\"),\n \"base64\"\n );\n const type = Buffer.from(\"ssh-rsa\");\n function encodeBuffer(buf: Buffer): Buffer {\n const len = Buffer.allocUnsafe(4);\n len.writeUInt32BE(buf.length, 0);\n return Buffer.concat([len, buf]);\n }\n const typeEncoded = encodeBuffer(type);\n const rsaKeyData = der;\n const base64Key = Buffer.concat([typeEncoded, rsaKeyData]).toString(\"base64\");\n return `ssh-rsa ${base64Key} pp-generated`;\n}\n\nexport async function generateAndImportKeyPair(ec2: EC2Client, keyName: string): Promise<{ privateKey: string }> {\n const { privateKey, publicKey } = generateSshKeyPair();\n await ec2.send(new ImportKeyPairCommand({\n KeyName: keyName,\n PublicKeyMaterial: Buffer.from(publicKey),\n }));\n return { privateKey };\n}\n\nexport async function createPreviewSecurityGroup(ec2: EC2Client, groupName: string, port: number): Promise {\n const describe = await ec2.send(new DescribeSecurityGroupsCommand({\n Filters: [{ Name: \"group-name\", Values: [groupName] }],\n }));\n if (describe.SecurityGroups && describe.SecurityGroups.length > 0) {\n return describe.SecurityGroups[0].GroupId!;\n }\n\n const res = await ec2.send(new CreateSecurityGroupCommand({\n GroupName: groupName,\n Description: `PP Preview security group: ${groupName}`,\n }));\n const groupId = res.GroupId!;\n\n await ec2.send(new AuthorizeSecurityGroupIngressCommand({\n GroupId: groupId,\n IpPermissions: [\n {\n IpProtocol: \"tcp\",\n FromPort: 22,\n ToPort: 22,\n IpRanges: [{ CidrIp: \"0.0.0.0/0\" }],\n },\n {\n IpProtocol: \"tcp\",\n FromPort: port,\n ToPort: port,\n IpRanges: [{ CidrIp: \"0.0.0.0/0\" }],\n },\n ],\n }));\n return groupId;\n}\n\nconst BOOTSTRAP_SCRIPT = `#!/bin/bash\nset -e\napt-get update -y\napt-get install -y curl git unzip build-essential\ncurl -fsSL https://get.docker.com | sh\nsystemctl enable docker\nsystemctl start docker\napt-get install -y docker-compose-plugin\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash\nexport NVM_DIR=\"/root/.nvm\"\nsource \"$NVM_DIR/nvm.sh\"\nnvm install --lts\nnvm alias default lts/*\n`;\n\nexport async function launchInstance(opts: {\n ec2: EC2Client;\n region: string;\n instanceType: string;\n keyName: string;\n securityGroupId: string;\n tags: Record;\n}): Promise {\n const ami = UBUNTU_22_04_AMI[opts.region] ?? UBUNTU_22_04_AMI[\"us-east-1\"];\n const tagSpecs = Object.entries(opts.tags).map(([k, v]) => ({ Key: k, Value: v }));\n tagSpecs.push({ Key: \"Name\", Value: `pp-preview-${opts.tags[\"pp:previewId\"]}` });\n\n const res = await opts.ec2.send(new RunInstancesCommand({\n ImageId: ami,\n InstanceType: opts.instanceType as any,\n MinCount: 1,\n MaxCount: 1,\n KeyName: opts.keyName,\n SecurityGroupIds: [opts.securityGroupId],\n UserData: Buffer.from(BOOTSTRAP_SCRIPT).toString(\"base64\"),\n TagSpecifications: [\n { ResourceType: \"instance\", Tags: tagSpecs },\n ],\n }));\n\n return res.Instances![0].InstanceId!;\n}\n\nexport async function waitForInstanceRunning(ec2: EC2Client, instanceId: string, maxWaitMs = 300_000): Promise {\n const start = Date.now();\n while (Date.now() - start < maxWaitMs) {\n const res = await ec2.send(new DescribeInstancesCommand({\n InstanceIds: [instanceId],\n }));\n const inst = res.Reservations?.[0]?.Instances?.[0];\n if (inst?.State?.Name === \"running\" && inst.PublicIpAddress) {\n return inst.PublicIpAddress;\n }\n await sleep(5000);\n }\n throw new Error(`Instance ${instanceId} did not reach running state within timeout`);\n}\n\nexport async function terminateInstance(ec2: EC2Client, instanceId: string): Promise {\n await ec2.send(new TerminateInstancesCommand({ InstanceIds: [instanceId] }));\n}\n\nexport async function deleteKeyPairAws(ec2: EC2Client, keyName: string): Promise {\n try {\n await ec2.send(new DeleteKeyPairCommand({ KeyName: keyName }));\n } catch (e) {\n log.warn({ e, keyName }, \"Failed to delete key pair\");\n }\n}\n\nexport async function deleteSecurityGroupAws(ec2: EC2Client, groupName: string): Promise {\n try {\n const describe = await ec2.send(new DescribeSecurityGroupsCommand({\n Filters: [{ Name: \"group-name\", Values: [groupName] }],\n }));\n const groupId = describe.SecurityGroups?.[0]?.GroupId;\n if (groupId) {\n await ec2.send(new DeleteSecurityGroupCommand({ GroupId: groupId }));\n }\n } catch (e) {\n log.warn({ e, groupName }, \"Failed to delete security group\");\n }\n}\n\nexport async function describeAllManagedInstances(ec2: EC2Client): Promise {\n const res = await ec2.send(new DescribeInstancesCommand({\n Filters: [{ Name: \"tag:pp:managed\", Values: [\"true\"] }, { Name: \"instance-state-name\", Values: [\"running\", \"pending\", \"stopping\", \"stopped\"] }],\n }));\n return (res.Reservations ?? []).flatMap(r => r.Instances ?? []);\n}\n\nfunction sleep(ms: number) {\n return new Promise(r => setTimeout(r, ms));\n}\n"], - "mappings": "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", + "sourcesContent": ["import {\n EC2Client,\n RunInstancesCommand,\n TerminateInstancesCommand,\n DescribeInstancesCommand,\n CreateSecurityGroupCommand,\n DeleteSecurityGroupCommand,\n AuthorizeSecurityGroupIngressCommand,\n DescribeSecurityGroupsCommand,\n ImportKeyPairCommand,\n DeleteKeyPairCommand,\n CreateTagsCommand,\n} from \"@aws-sdk/client-ec2\";\nimport { STSClient, GetCallerIdentityCommand } from \"@aws-sdk/client-sts\";\nimport { generateKeyPairSync, createPublicKey } from \"crypto\";\nimport { createLogger } from \"../lib/logger\";\nimport { decrypt } from \"../lib/encryption\";\nimport type { User } from \"@prisma/client\";\n\nconst log = createLogger(\"EC2\");\n\nconst UBUNTU_22_04_AMI: Record = {\n \"us-east-1\": \"ami-0e86e20dae9224db8\",\n \"us-east-2\": \"ami-0a0d9cf81c479446a\",\n \"us-west-1\": \"ami-05c969369880fa2c2\",\n \"us-west-2\": \"ami-03f8acd418785369b\",\n \"eu-west-1\": \"ami-0694d931cee176e7d\",\n \"eu-west-2\": \"ami-0f3d9639a5674d559\",\n \"eu-west-3\": \"ami-022e307f4b9e39f45\",\n \"eu-central-1\": \"ami-0faab6bdbac9486fb\",\n \"ap-southeast-1\": \"ami-0823c236601fef765\",\n \"ap-southeast-2\": \"ami-07620139298af599e\",\n \"ap-northeast-1\": \"ami-0b7546e839d7ace12\",\n \"ap-northeast-2\": \"ami-042e76978adeb8c48\",\n \"ap-south-1\": \"ami-076e3a557efe1aa9c\",\n \"sa-east-1\": \"ami-0eed58016fbe42de3\",\n \"ca-central-1\": \"ami-024f768de9e73d4f4\",\n \"eu-north-1\": \"ami-00381a880aa48c6c6\",\n \"me-south-1\": \"ami-09574f34b8dcd2eac\",\n \"af-south-1\": \"ami-08fdcf06b39fe83ec\",\n};\n\nexport function makeEc2Client(user: User): EC2Client {\n const accessKeyId = user.awsAccessKeyId ? decrypt(user.awsAccessKeyId) : \"\";\n const secretAccessKey = user.awsSecretAccessKey ? decrypt(user.awsSecretAccessKey) : \"\";\n return new EC2Client({\n region: user.awsRegion!,\n credentials: { accessKeyId, secretAccessKey },\n });\n}\n\nexport function makeStsClient(user: User): STSClient {\n const accessKeyId = user.awsAccessKeyId ? decrypt(user.awsAccessKeyId) : \"\";\n const secretAccessKey = user.awsSecretAccessKey ? decrypt(user.awsSecretAccessKey) : \"\";\n return new STSClient({\n region: user.awsRegion!,\n credentials: { accessKeyId, secretAccessKey },\n });\n}\n\nexport async function validateAwsCredentials(user: User): Promise<{ success: boolean; arn?: string; error?: string }> {\n try {\n const sts = makeStsClient(user);\n const res = await sts.send(new GetCallerIdentityCommand({}));\n return { success: true, arn: res.Arn };\n } catch (e: any) {\n return { success: false, error: e.message };\n }\n}\n\nexport function generateSshKeyPair(): { privateKey: string; publicKeyOpenssh: string } {\n const { privateKey: privKeyPem, publicKey: pubKeyPem } = generateKeyPairSync(\"rsa\", {\n modulusLength: 2048,\n publicKeyEncoding: { type: \"spki\", format: \"pem\" },\n privateKeyEncoding: { type: \"pkcs1\", format: \"pem\" },\n });\n\n const pubKeyObj = createPublicKey(pubKeyPem);\n const pubKeyDer = pubKeyObj.export({ type: \"spki\", format: \"der\" });\n\n function sshEncodeBuffer(buf: Buffer): Buffer {\n const lenBuf = Buffer.allocUnsafe(4);\n lenBuf.writeUInt32BE(buf.length, 0);\n return Buffer.concat([lenBuf, buf]);\n }\n\n const keyTypeStr = Buffer.from(\"ssh-rsa\");\n const keyTypeEncoded = sshEncodeBuffer(keyTypeStr);\n\n const base64Encoded = pubKeyDer.toString(\"base64\");\n const openSshKey = `ssh-rsa ${Buffer.concat([keyTypeEncoded]).toString(\"base64\")} pp-generated`;\n\n return { privateKey: privKeyPem, publicKeyOpenssh: `ssh-rsa ${base64Encoded} pp-generated` };\n}\n\nexport async function generateAndImportKeyPair(ec2: EC2Client, keyName: string): Promise<{ privateKey: string }> {\n const { privateKey, publicKeyOpenssh } = generateSshKeyPair();\n await ec2.send(new ImportKeyPairCommand({\n KeyName: keyName,\n PublicKeyMaterial: Buffer.from(publicKeyOpenssh),\n }));\n return { privateKey };\n}\n\nexport async function createPreviewSecurityGroup(ec2: EC2Client, groupName: string, port: number): Promise {\n const describe = await ec2.send(new DescribeSecurityGroupsCommand({\n Filters: [{ Name: \"group-name\", Values: [groupName] }],\n }));\n if (describe.SecurityGroups && describe.SecurityGroups.length > 0) {\n return describe.SecurityGroups[0].GroupId!;\n }\n\n const res = await ec2.send(new CreateSecurityGroupCommand({\n GroupName: groupName,\n Description: `PP Preview security group: ${groupName}`,\n }));\n const groupId = res.GroupId!;\n\n const ingress: any[] = [\n {\n IpProtocol: \"tcp\",\n FromPort: 22,\n ToPort: 22,\n IpRanges: [{ CidrIp: \"0.0.0.0/0\" }],\n },\n ];\n if (port !== 22) {\n ingress.push({\n IpProtocol: \"tcp\",\n FromPort: port,\n ToPort: port,\n IpRanges: [{ CidrIp: \"0.0.0.0/0\" }],\n });\n }\n\n await ec2.send(new AuthorizeSecurityGroupIngressCommand({\n GroupId: groupId,\n IpPermissions: ingress,\n }));\n return groupId;\n}\n\nconst BOOTSTRAP_SCRIPT = `#!/bin/bash\nset -e\napt-get update -y\napt-get install -y curl git unzip build-essential\ncurl -fsSL https://get.docker.com | sh\nsystemctl enable docker\nsystemctl start docker\napt-get install -y docker-compose-plugin\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash\nexport NVM_DIR=\"/root/.nvm\"\nsource \"$NVM_DIR/nvm.sh\"\nnvm install --lts\nnvm alias default lts/*\n`;\n\nexport async function launchInstance(opts: {\n ec2: EC2Client;\n region: string;\n instanceType: string;\n keyName: string;\n securityGroupId: string;\n tags: Record;\n}): Promise {\n const ami = UBUNTU_22_04_AMI[opts.region] ?? UBUNTU_22_04_AMI[\"us-east-1\"];\n const tagSpecs = Object.entries(opts.tags).map(([k, v]) => ({ Key: k, Value: v }));\n tagSpecs.push({ Key: \"Name\", Value: `pp-preview-${opts.tags[\"pp:previewId\"]}` });\n\n const res = await opts.ec2.send(new RunInstancesCommand({\n ImageId: ami,\n InstanceType: opts.instanceType as any,\n MinCount: 1,\n MaxCount: 1,\n KeyName: opts.keyName,\n SecurityGroupIds: [opts.securityGroupId],\n UserData: Buffer.from(BOOTSTRAP_SCRIPT).toString(\"base64\"),\n TagSpecifications: [\n { ResourceType: \"instance\", Tags: tagSpecs },\n ],\n }));\n\n return res.Instances![0].InstanceId!;\n}\n\nexport async function waitForInstanceRunning(ec2: EC2Client, instanceId: string, maxWaitMs = 300_000): Promise {\n const start = Date.now();\n while (Date.now() - start < maxWaitMs) {\n const res = await ec2.send(new DescribeInstancesCommand({\n InstanceIds: [instanceId],\n }));\n const inst = res.Reservations?.[0]?.Instances?.[0];\n if (inst?.State?.Name === \"running\" && inst.PublicIpAddress) {\n return inst.PublicIpAddress;\n }\n await sleep(5000);\n }\n throw new Error(`Instance ${instanceId} did not reach running state within timeout`);\n}\n\nexport async function terminateInstance(ec2: EC2Client, instanceId: string): Promise {\n await ec2.send(new TerminateInstancesCommand({ InstanceIds: [instanceId] }));\n}\n\nexport async function deleteKeyPairAws(ec2: EC2Client, keyName: string): Promise {\n try {\n await ec2.send(new DeleteKeyPairCommand({ KeyName: keyName }));\n } catch (e) {\n log.warn({ e, keyName }, \"Failed to delete key pair\");\n }\n}\n\nexport async function deleteSecurityGroupAws(ec2: EC2Client, groupName: string): Promise {\n try {\n const describe = await ec2.send(new DescribeSecurityGroupsCommand({\n Filters: [{ Name: \"group-name\", Values: [groupName] }],\n }));\n const groupId = describe.SecurityGroups?.[0]?.GroupId;\n if (groupId) {\n await sleep(5000);\n await ec2.send(new DeleteSecurityGroupCommand({ GroupId: groupId }));\n }\n } catch (e) {\n log.warn({ e, groupName }, \"Failed to delete security group\");\n }\n}\n\nexport async function describeAllManagedInstances(ec2: EC2Client): Promise {\n const res = await ec2.send(new DescribeInstancesCommand({\n Filters: [\n { Name: \"tag:pp:managed\", Values: [\"true\"] },\n { Name: \"instance-state-name\", Values: [\"running\", \"pending\", \"stopping\"] },\n ],\n }));\n return (res.Reservations ?? []).flatMap(r => r.Instances ?? []);\n}\n\nfunction sleep(ms: number) {\n return new Promise(r => setTimeout(r, ms));\n}\n"], + "mappings": "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", "names": [] } diff --git a/backend/dist/services/orphanCleanup.js b/backend/dist/services/orphanCleanup.js new file mode 100644 index 00000000..721a6df4 --- /dev/null +++ b/backend/dist/services/orphanCleanup.js @@ -0,0 +1,74 @@ +"use strict"; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var orphanCleanup_exports = {}; +__export(orphanCleanup_exports, { + runOrphanCleanup: () => runOrphanCleanup +}); +module.exports = __toCommonJS(orphanCleanup_exports); +var import_db = require("../lib/db"); +var import_logger = require("../lib/logger"); +var import_ec2 = require("./ec2"); +const log = (0, import_logger.createLogger)("ORPHAN_CLEANUP"); +async function runOrphanCleanup() { + log.info("Running orphan EC2 instance cleanup"); + const users = await import_db.prisma.user.findMany({ + where: { + awsAccessKeyId: { not: null }, + awsSecretAccessKey: { not: null }, + awsRegion: { not: null } + } + }); + const activePreviews = await import_db.prisma.preview.findMany({ + where: { status: { notIn: ["STOPPED", "IGNORED"] }, instanceId: { not: null } }, + select: { id: true, instanceId: true } + }); + const activeInstanceIds = new Set(activePreviews.map((p) => p.instanceId)); + for (const user of users) { + try { + const ec2 = (0, import_ec2.makeEc2Client)(user); + const instances = await (0, import_ec2.describeAllManagedInstances)(ec2); + for (const inst of instances) { + const instanceId = inst.InstanceId; + if (!instanceId) continue; + const tags = Object.fromEntries((inst.Tags || []).map((t) => [t.Key, t.Value])); + const previewId = parseInt(tags["pp:previewId"] || "0", 10); + if (!activeInstanceIds.has(instanceId)) { + log.info({ instanceId, previewId }, "Terminating orphan instance"); + try { + const keyName = `pp-preview-${previewId}`; + const sgName = `pp-preview-${previewId}`; + await (0, import_ec2.deleteKeyPairAws)(ec2, keyName); + await (0, import_ec2.terminateInstance)(ec2, instanceId); + setTimeout(() => (0, import_ec2.deleteSecurityGroupAws)(ec2, sgName), 3e4); + } catch (e) { + log.warn({ e, instanceId }, "Failed to terminate orphan"); + } + } + } + } catch (e) { + log.warn({ e, userId: user.id }, "Failed to check orphans for user"); + } + } + log.info("Orphan cleanup complete"); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + runOrphanCleanup +}); +//# sourceMappingURL=orphanCleanup.js.map diff --git a/backend/dist/services/orphanCleanup.js.map b/backend/dist/services/orphanCleanup.js.map new file mode 100644 index 00000000..51725c23 --- /dev/null +++ b/backend/dist/services/orphanCleanup.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../src/services/orphanCleanup.ts"], + "sourcesContent": ["import { prisma } from \"../lib/db\";\nimport { createLogger } from \"../lib/logger\";\nimport { makeEc2Client, describeAllManagedInstances, terminateInstance, deleteKeyPairAws, deleteSecurityGroupAws } from \"./ec2\";\n\nconst log = createLogger(\"ORPHAN_CLEANUP\");\n\nexport async function runOrphanCleanup() {\n log.info(\"Running orphan EC2 instance cleanup\");\n\n const users = await prisma.user.findMany({\n where: {\n awsAccessKeyId: { not: null },\n awsSecretAccessKey: { not: null },\n awsRegion: { not: null },\n },\n });\n\n const activePreviews = await prisma.preview.findMany({\n where: { status: { notIn: [\"STOPPED\", \"IGNORED\"] }, instanceId: { not: null } },\n select: { id: true, instanceId: true },\n });\n const activeInstanceIds = new Set(activePreviews.map(p => p.instanceId!));\n\n for (const user of users) {\n try {\n const ec2 = makeEc2Client(user as any);\n const instances = await describeAllManagedInstances(ec2);\n\n for (const inst of instances) {\n const instanceId = inst.InstanceId;\n if (!instanceId) continue;\n\n const tags = Object.fromEntries((inst.Tags || []).map((t: any) => [t.Key, t.Value]));\n const previewId = parseInt(tags[\"pp:previewId\"] || \"0\", 10);\n\n if (!activeInstanceIds.has(instanceId)) {\n log.info({ instanceId, previewId }, \"Terminating orphan instance\");\n try {\n const keyName = `pp-preview-${previewId}`;\n const sgName = `pp-preview-${previewId}`;\n await deleteKeyPairAws(ec2, keyName);\n await terminateInstance(ec2, instanceId);\n setTimeout(() => deleteSecurityGroupAws(ec2, sgName), 30_000);\n } catch (e) {\n log.warn({ e, instanceId }, \"Failed to terminate orphan\");\n }\n }\n }\n } catch (e) {\n log.warn({ e, userId: user.id }, \"Failed to check orphans for user\");\n }\n }\n\n log.info(\"Orphan cleanup complete\");\n}\n"], + "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAuB;AACvB,oBAA6B;AAC7B,iBAAwH;AAExH,MAAM,UAAM,4BAAa,gBAAgB;AAEzC,eAAsB,mBAAmB;AACvC,MAAI,KAAK,qCAAqC;AAE9C,QAAM,QAAQ,MAAM,iBAAO,KAAK,SAAS;AAAA,IACvC,OAAO;AAAA,MACL,gBAAgB,EAAE,KAAK,KAAK;AAAA,MAC5B,oBAAoB,EAAE,KAAK,KAAK;AAAA,MAChC,WAAW,EAAE,KAAK,KAAK;AAAA,IACzB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,MAAM,iBAAO,QAAQ,SAAS;AAAA,IACnD,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,WAAW,SAAS,EAAE,GAAG,YAAY,EAAE,KAAK,KAAK,EAAE;AAAA,IAC9E,QAAQ,EAAE,IAAI,MAAM,YAAY,KAAK;AAAA,EACvC,CAAC;AACD,QAAM,oBAAoB,IAAI,IAAI,eAAe,IAAI,OAAK,EAAE,UAAW,CAAC;AAExE,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,YAAM,UAAM,0BAAc,IAAW;AACrC,YAAM,YAAY,UAAM,wCAA4B,GAAG;AAEvD,iBAAW,QAAQ,WAAW;AAC5B,cAAM,aAAa,KAAK;AACxB,YAAI,CAAC,WAAY;AAEjB,cAAM,OAAO,OAAO,aAAa,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAW,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AACnF,cAAM,YAAY,SAAS,KAAK,cAAc,KAAK,KAAK,EAAE;AAE1D,YAAI,CAAC,kBAAkB,IAAI,UAAU,GAAG;AACtC,cAAI,KAAK,EAAE,YAAY,UAAU,GAAG,6BAA6B;AACjE,cAAI;AACF,kBAAM,UAAU,cAAc,SAAS;AACvC,kBAAM,SAAS,cAAc,SAAS;AACtC,sBAAM,6BAAiB,KAAK,OAAO;AACnC,sBAAM,8BAAkB,KAAK,UAAU;AACvC,uBAAW,UAAM,mCAAuB,KAAK,MAAM,GAAG,GAAM;AAAA,UAC9D,SAAS,GAAG;AACV,gBAAI,KAAK,EAAE,GAAG,WAAW,GAAG,4BAA4B;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,UAAI,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,GAAG,kCAAkC;AAAA,IACrE;AAAA,EACF;AAEA,MAAI,KAAK,yBAAyB;AACpC;", + "names": [] +} diff --git a/backend/src/index.ts b/backend/src/index.ts index fe67bfca..9ff5bd58 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -13,6 +13,7 @@ import { ERROR_MESSAGES } from "./lib/errors"; import { startJobWorker } from "./workers/jobWorker"; import { startCronWorkers } from "./workers/cronWorker"; import { getAdminSettings } from "./lib/adminSettings"; +import { runOrphanCleanup } from "./services/orphanCleanup"; import { loginHandler, logoutHandler, meHandler, setupStatusHandler, firstUserHandler } from "./routes/auth"; import { webhookHandler } from "./routes/webhook"; @@ -125,6 +126,7 @@ server await getAdminSettings(); startJobWorker(); startCronWorkers(); + runOrphanCleanup().catch(e => logger.warn(e, "Orphan cleanup error")); logger.info("All workers started"); }) .catch((err) => logger.error(err, "Server failed to start")); diff --git a/backend/src/services/ec2.ts b/backend/src/services/ec2.ts index 91e5ce0f..0a2bbe1d 100644 --- a/backend/src/services/ec2.ts +++ b/backend/src/services/ec2.ts @@ -12,7 +12,7 @@ import { CreateTagsCommand, } from "@aws-sdk/client-ec2"; import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts"; -import { generateKeyPairSync } from "crypto"; +import { generateKeyPairSync, createPublicKey } from "crypto"; import { createLogger } from "../lib/logger"; import { decrypt } from "../lib/encryption"; import type { User } from "@prisma/client"; @@ -68,46 +68,36 @@ export async function validateAwsCredentials(user: User): Promise<{ success: boo } } -export function generateSshKeyPair(): { privateKey: string; publicKey: string } { - const { privateKey, publicKey } = generateKeyPairSync("rsa", { +export function generateSshKeyPair(): { privateKey: string; publicKeyOpenssh: string } { + const { privateKey: privKeyPem, publicKey: pubKeyPem } = generateKeyPairSync("rsa", { modulusLength: 2048, - publicKeyEncoding: { type: "pkcs1", format: "pem" }, + publicKeyEncoding: { type: "spki", format: "pem" }, privateKeyEncoding: { type: "pkcs1", format: "pem" }, }); - const pubKeyOpenSsh = rsaPemToOpenSsh(publicKey); - return { privateKey, publicKey: pubKeyOpenSsh }; -} -function rsaPemToOpenSsh(pem: string): string { - const { publicKeyEncoding } = generateKeyPairSync("rsa", { - modulusLength: 2048, - publicKeyEncoding: { type: "pkcs8", format: "pem" }, - privateKeyEncoding: { type: "pkcs8", format: "pem" }, - }); - void publicKeyEncoding; - const der = Buffer.from( - pem.replace(/-----BEGIN RSA PUBLIC KEY-----/, "") - .replace(/-----END RSA PUBLIC KEY-----/, "") - .replace(/\n/g, ""), - "base64" - ); - const type = Buffer.from("ssh-rsa"); - function encodeBuffer(buf: Buffer): Buffer { - const len = Buffer.allocUnsafe(4); - len.writeUInt32BE(buf.length, 0); - return Buffer.concat([len, buf]); + const pubKeyObj = createPublicKey(pubKeyPem); + const pubKeyDer = pubKeyObj.export({ type: "spki", format: "der" }); + + function sshEncodeBuffer(buf: Buffer): Buffer { + const lenBuf = Buffer.allocUnsafe(4); + lenBuf.writeUInt32BE(buf.length, 0); + return Buffer.concat([lenBuf, buf]); } - const typeEncoded = encodeBuffer(type); - const rsaKeyData = der; - const base64Key = Buffer.concat([typeEncoded, rsaKeyData]).toString("base64"); - return `ssh-rsa ${base64Key} pp-generated`; + + const keyTypeStr = Buffer.from("ssh-rsa"); + const keyTypeEncoded = sshEncodeBuffer(keyTypeStr); + + const base64Encoded = pubKeyDer.toString("base64"); + const openSshKey = `ssh-rsa ${Buffer.concat([keyTypeEncoded]).toString("base64")} pp-generated`; + + return { privateKey: privKeyPem, publicKeyOpenssh: `ssh-rsa ${base64Encoded} pp-generated` }; } export async function generateAndImportKeyPair(ec2: EC2Client, keyName: string): Promise<{ privateKey: string }> { - const { privateKey, publicKey } = generateSshKeyPair(); + const { privateKey, publicKeyOpenssh } = generateSshKeyPair(); await ec2.send(new ImportKeyPairCommand({ KeyName: keyName, - PublicKeyMaterial: Buffer.from(publicKey), + PublicKeyMaterial: Buffer.from(publicKeyOpenssh), })); return { privateKey }; } @@ -126,22 +116,26 @@ export async function createPreviewSecurityGroup(ec2: EC2Client, groupName: stri })); const groupId = res.GroupId!; + const ingress: any[] = [ + { + IpProtocol: "tcp", + FromPort: 22, + ToPort: 22, + IpRanges: [{ CidrIp: "0.0.0.0/0" }], + }, + ]; + if (port !== 22) { + ingress.push({ + IpProtocol: "tcp", + FromPort: port, + ToPort: port, + IpRanges: [{ CidrIp: "0.0.0.0/0" }], + }); + } + await ec2.send(new AuthorizeSecurityGroupIngressCommand({ GroupId: groupId, - IpPermissions: [ - { - IpProtocol: "tcp", - FromPort: 22, - ToPort: 22, - IpRanges: [{ CidrIp: "0.0.0.0/0" }], - }, - { - IpProtocol: "tcp", - FromPort: port, - ToPort: port, - IpRanges: [{ CidrIp: "0.0.0.0/0" }], - }, - ], + IpPermissions: ingress, })); return groupId; } @@ -223,6 +217,7 @@ export async function deleteSecurityGroupAws(ec2: EC2Client, groupName: string): })); const groupId = describe.SecurityGroups?.[0]?.GroupId; if (groupId) { + await sleep(5000); await ec2.send(new DeleteSecurityGroupCommand({ GroupId: groupId })); } } catch (e) { @@ -232,7 +227,10 @@ export async function deleteSecurityGroupAws(ec2: EC2Client, groupName: string): export async function describeAllManagedInstances(ec2: EC2Client): Promise { const res = await ec2.send(new DescribeInstancesCommand({ - Filters: [{ Name: "tag:pp:managed", Values: ["true"] }, { Name: "instance-state-name", Values: ["running", "pending", "stopping", "stopped"] }], + Filters: [ + { Name: "tag:pp:managed", Values: ["true"] }, + { Name: "instance-state-name", Values: ["running", "pending", "stopping"] }, + ], })); return (res.Reservations ?? []).flatMap(r => r.Instances ?? []); } diff --git a/backend/src/services/orphanCleanup.ts b/backend/src/services/orphanCleanup.ts new file mode 100644 index 00000000..37c8bc7e --- /dev/null +++ b/backend/src/services/orphanCleanup.ts @@ -0,0 +1,55 @@ +import { prisma } from "../lib/db"; +import { createLogger } from "../lib/logger"; +import { makeEc2Client, describeAllManagedInstances, terminateInstance, deleteKeyPairAws, deleteSecurityGroupAws } from "./ec2"; + +const log = createLogger("ORPHAN_CLEANUP"); + +export async function runOrphanCleanup() { + log.info("Running orphan EC2 instance cleanup"); + + const users = await prisma.user.findMany({ + where: { + awsAccessKeyId: { not: null }, + awsSecretAccessKey: { not: null }, + awsRegion: { not: null }, + }, + }); + + const activePreviews = await prisma.preview.findMany({ + where: { status: { notIn: ["STOPPED", "IGNORED"] }, instanceId: { not: null } }, + select: { id: true, instanceId: true }, + }); + const activeInstanceIds = new Set(activePreviews.map(p => p.instanceId!)); + + for (const user of users) { + try { + const ec2 = makeEc2Client(user as any); + const instances = await describeAllManagedInstances(ec2); + + for (const inst of instances) { + const instanceId = inst.InstanceId; + if (!instanceId) continue; + + const tags = Object.fromEntries((inst.Tags || []).map((t: any) => [t.Key, t.Value])); + const previewId = parseInt(tags["pp:previewId"] || "0", 10); + + if (!activeInstanceIds.has(instanceId)) { + log.info({ instanceId, previewId }, "Terminating orphan instance"); + try { + const keyName = `pp-preview-${previewId}`; + const sgName = `pp-preview-${previewId}`; + await deleteKeyPairAws(ec2, keyName); + await terminateInstance(ec2, instanceId); + setTimeout(() => deleteSecurityGroupAws(ec2, sgName), 30_000); + } catch (e) { + log.warn({ e, instanceId }, "Failed to terminate orphan"); + } + } + } + } catch (e) { + log.warn({ e, userId: user.id }, "Failed to check orphans for user"); + } + } + + log.info("Orphan cleanup complete"); +} diff --git a/frontend/dist/assets/index-BhM-WgUO.js b/frontend/dist/assets/index-BhM-WgUO.js new file mode 100644 index 00000000..080891ea --- /dev/null +++ b/frontend/dist/assets/index-BhM-WgUO.js @@ -0,0 +1,905 @@ +(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))r(u);new MutationObserver(u=>{for(const d of u)if(d.type==="childList")for(const f of d.addedNodes)f.tagName==="LINK"&&f.rel==="modulepreload"&&r(f)}).observe(document,{childList:!0,subtree:!0});function l(u){const d={};return u.integrity&&(d.integrity=u.integrity),u.referrerPolicy&&(d.referrerPolicy=u.referrerPolicy),u.crossOrigin==="use-credentials"?d.credentials="include":u.crossOrigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function r(u){if(u.ep)return;u.ep=!0;const d=l(u);fetch(u.href,d)}})();function mg(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var zc={exports:{}},Ls={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Cy;function J1(){if(Cy)return Ls;Cy=1;var n=Symbol.for("react.transitional.element"),i=Symbol.for("react.fragment");function l(r,u,d){var f=null;if(d!==void 0&&(f=""+d),u.key!==void 0&&(f=""+u.key),"key"in u){d={};for(var h in u)h!=="key"&&(d[h]=u[h])}else d=u;return u=d.ref,{$$typeof:n,type:r,key:f,ref:u!==void 0?u:null,props:d}}return Ls.Fragment=i,Ls.jsx=l,Ls.jsxs=l,Ls}var wy;function W1(){return wy||(wy=1,zc.exports=J1()),zc.exports}var g=W1(),Lc={exports:{}},ft={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Ry;function I1(){if(Ry)return ft;Ry=1;var n=Symbol.for("react.transitional.element"),i=Symbol.for("react.portal"),l=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),u=Symbol.for("react.profiler"),d=Symbol.for("react.consumer"),f=Symbol.for("react.context"),h=Symbol.for("react.forward_ref"),m=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),v=Symbol.for("react.lazy"),x=Symbol.for("react.activity"),S=Symbol.iterator;function E(N){return N===null||typeof N!="object"?null:(N=S&&N[S]||N["@@iterator"],typeof N=="function"?N:null)}var A={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},w=Object.assign,_={};function R(N,K,et){this.props=N,this.context=K,this.refs=_,this.updater=et||A}R.prototype.isReactComponent={},R.prototype.setState=function(N,K){if(typeof N!="object"&&typeof N!="function"&&N!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,N,K,"setState")},R.prototype.forceUpdate=function(N){this.updater.enqueueForceUpdate(this,N,"forceUpdate")};function O(){}O.prototype=R.prototype;function D(N,K,et){this.props=N,this.context=K,this.refs=_,this.updater=et||A}var M=D.prototype=new O;M.constructor=D,w(M,R.prototype),M.isPureReactComponent=!0;var H=Array.isArray;function $(){}var V={H:null,A:null,T:null,S:null},q=Object.prototype.hasOwnProperty;function Z(N,K,et){var it=et.ref;return{$$typeof:n,type:N,key:K,ref:it!==void 0?it:null,props:et}}function F(N,K){return Z(N.type,K,N.props)}function I(N){return typeof N=="object"&&N!==null&&N.$$typeof===n}function ut(N){var K={"=":"=0",":":"=2"};return"$"+N.replace(/[=:]/g,function(et){return K[et]})}var Tt=/\/+/g;function pt(N,K){return typeof N=="object"&&N!==null&&N.key!=null?ut(""+N.key):K.toString(36)}function Q(N){switch(N.status){case"fulfilled":return N.value;case"rejected":throw N.reason;default:switch(typeof N.status=="string"?N.then($,$):(N.status="pending",N.then(function(K){N.status==="pending"&&(N.status="fulfilled",N.value=K)},function(K){N.status==="pending"&&(N.status="rejected",N.reason=K)})),N.status){case"fulfilled":return N.value;case"rejected":throw N.reason}}throw N}function L(N,K,et,it,dt){var yt=typeof N;(yt==="undefined"||yt==="boolean")&&(N=null);var wt=!1;if(N===null)wt=!0;else switch(yt){case"bigint":case"string":case"number":wt=!0;break;case"object":switch(N.$$typeof){case n:case i:wt=!0;break;case v:return wt=N._init,L(wt(N._payload),K,et,it,dt)}}if(wt)return dt=dt(N),wt=it===""?"."+pt(N,0):it,H(dt)?(et="",wt!=null&&(et=wt.replace(Tt,"$&/")+"/"),L(dt,K,et,"",function(Yi){return Yi})):dt!=null&&(I(dt)&&(dt=F(dt,et+(dt.key==null||N&&N.key===dt.key?"":(""+dt.key).replace(Tt,"$&/")+"/")+wt)),K.push(dt)),1;wt=0;var fe=it===""?".":it+":";if(H(N))for(var Xt=0;Xt>>1,ct=L[W];if(0>>1;Wu(et,tt))itu(dt,et)?(L[W]=dt,L[it]=tt,W=it):(L[W]=et,L[K]=tt,W=K);else if(itu(dt,tt))L[W]=dt,L[it]=tt,W=it;else break t}}return J}function u(L,J){var tt=L.sortIndex-J.sortIndex;return tt!==0?tt:L.id-J.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var d=performance;n.unstable_now=function(){return d.now()}}else{var f=Date,h=f.now();n.unstable_now=function(){return f.now()-h}}var m=[],p=[],v=1,x=null,S=3,E=!1,A=!1,w=!1,_=!1,R=typeof setTimeout=="function"?setTimeout:null,O=typeof clearTimeout=="function"?clearTimeout:null,D=typeof setImmediate<"u"?setImmediate:null;function M(L){for(var J=l(p);J!==null;){if(J.callback===null)r(p);else if(J.startTime<=L)r(p),J.sortIndex=J.expirationTime,i(m,J);else break;J=l(p)}}function H(L){if(w=!1,M(L),!A)if(l(m)!==null)A=!0,$||($=!0,ut());else{var J=l(p);J!==null&&Q(H,J.startTime-L)}}var $=!1,V=-1,q=5,Z=-1;function F(){return _?!0:!(n.unstable_now()-ZL&&F());){var W=x.callback;if(typeof W=="function"){x.callback=null,S=x.priorityLevel;var ct=W(x.expirationTime<=L);if(L=n.unstable_now(),typeof ct=="function"){x.callback=ct,M(L),J=!0;break e}x===l(m)&&r(m),M(L)}else r(m);x=l(m)}if(x!==null)J=!0;else{var N=l(p);N!==null&&Q(H,N.startTime-L),J=!1}}break t}finally{x=null,S=tt,E=!1}J=void 0}}finally{J?ut():$=!1}}}var ut;if(typeof D=="function")ut=function(){D(I)};else if(typeof MessageChannel<"u"){var Tt=new MessageChannel,pt=Tt.port2;Tt.port1.onmessage=I,ut=function(){pt.postMessage(null)}}else ut=function(){R(I,0)};function Q(L,J){V=R(function(){L(n.unstable_now())},J)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(L){L.callback=null},n.unstable_forceFrameRate=function(L){0>L||125W?(L.sortIndex=tt,i(p,L),l(m)===null&&L===l(p)&&(w?(O(V),V=-1):w=!0,Q(H,tt-W))):(L.sortIndex=ct,i(m,L),A||E||(A=!0,$||($=!0,ut()))),L},n.unstable_shouldYield=F,n.unstable_wrapCallback=function(L){var J=S;return function(){var tt=S;S=J;try{return L.apply(this,arguments)}finally{S=tt}}}})(Vc)),Vc}var jy;function eS(){return jy||(jy=1,Uc.exports=tS()),Uc.exports}var Bc={exports:{}},ce={};/** + * @license React + * react-dom.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Dy;function nS(){if(Dy)return ce;Dy=1;var n=Vf();function i(m){var p="https://react.dev/errors/"+m;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(i){console.error(i)}}return n(),Bc.exports=nS(),Bc.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Oy;function iS(){if(Oy)return ks;Oy=1;var n=eS(),i=Vf(),l=aS();function r(t){var e="https://react.dev/errors/"+t;if(1ct||(t.current=W[ct],W[ct]=null,ct--)}function et(t,e){ct++,W[ct]=t.current,t.current=e}var it=N(null),dt=N(null),yt=N(null),wt=N(null);function fe(t,e){switch(et(yt,e),et(dt,t),et(it,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?Zp(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=Zp(e),t=$p(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}K(it),et(it,t)}function Xt(){K(it),K(dt),K(yt)}function Yi(t){t.memoizedState!==null&&et(wt,t);var e=it.current,a=$p(e,t.type);e!==a&&(et(dt,t),et(it,a))}function rl(t){dt.current===t&&(K(it),K(dt)),wt.current===t&&(K(wt),Ds._currentValue=tt)}var yo,Ad;function ga(t){if(yo===void 0)try{throw Error()}catch(a){var e=a.stack.trim().match(/\n( *(at )?)/);yo=e&&e[1]||"",Ad=-1)":-1o||C[s]!==U[o]){var Y=` +`+C[s].replace(" at new "," at ");return t.displayName&&Y.includes("")&&(Y=Y.replace("",t.displayName)),Y}while(1<=s&&0<=o);break}}}finally{go=!1,Error.prepareStackTrace=a}return(a=t?t.displayName||t.name:"")?ga(a):""}function _x(t,e){switch(t.tag){case 26:case 27:case 5:return ga(t.type);case 16:return ga("Lazy");case 13:return t.child!==e&&e!==null?ga("Suspense Fallback"):ga("Suspense");case 19:return ga("SuspenseList");case 0:case 15:return vo(t.type,!1);case 11:return vo(t.type.render,!1);case 1:return vo(t.type,!0);case 31:return ga("Activity");default:return""}}function Cd(t){try{var e="",a=null;do e+=_x(t,a),a=t,t=t.return;while(t);return e}catch(s){return` +Error generating stack: `+s.message+` +`+s.stack}}var xo=Object.prototype.hasOwnProperty,bo=n.unstable_scheduleCallback,So=n.unstable_cancelCallback,Nx=n.unstable_shouldYield,jx=n.unstable_requestPaint,Ee=n.unstable_now,Dx=n.unstable_getCurrentPriorityLevel,wd=n.unstable_ImmediatePriority,Rd=n.unstable_UserBlockingPriority,ol=n.unstable_NormalPriority,Mx=n.unstable_LowPriority,_d=n.unstable_IdlePriority,Ox=n.log,zx=n.unstable_setDisableYieldValue,qi=null,Ae=null;function Vn(t){if(typeof Ox=="function"&&zx(t),Ae&&typeof Ae.setStrictMode=="function")try{Ae.setStrictMode(qi,t)}catch{}}var Ce=Math.clz32?Math.clz32:Ux,Lx=Math.log,kx=Math.LN2;function Ux(t){return t>>>=0,t===0?32:31-(Lx(t)/kx|0)|0}var ul=256,cl=262144,fl=4194304;function va(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function dl(t,e,a){var s=t.pendingLanes;if(s===0)return 0;var o=0,c=t.suspendedLanes,y=t.pingedLanes;t=t.warmLanes;var b=s&134217727;return b!==0?(s=b&~c,s!==0?o=va(s):(y&=b,y!==0?o=va(y):a||(a=b&~t,a!==0&&(o=va(a))))):(b=s&~c,b!==0?o=va(b):y!==0?o=va(y):a||(a=s&~t,a!==0&&(o=va(a)))),o===0?0:e!==0&&e!==o&&(e&c)===0&&(c=o&-o,a=e&-e,c>=a||c===32&&(a&4194048)!==0)?e:o}function Pi(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function Vx(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Nd(){var t=fl;return fl<<=1,(fl&62914560)===0&&(fl=4194304),t}function To(t){for(var e=[],a=0;31>a;a++)e.push(t);return e}function Xi(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function Bx(t,e,a,s,o,c){var y=t.pendingLanes;t.pendingLanes=a,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=a,t.entangledLanes&=a,t.errorRecoveryDisabledLanes&=a,t.shellSuspendCounter=0;var b=t.entanglements,C=t.expirationTimes,U=t.hiddenUpdates;for(a=y&~a;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var Xx=/[\n"\\]/g;function ke(t){return t.replace(Xx,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function _o(t,e,a,s,o,c,y,b){t.name="",y!=null&&typeof y!="function"&&typeof y!="symbol"&&typeof y!="boolean"?t.type=y:t.removeAttribute("type"),e!=null?y==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+Le(e)):t.value!==""+Le(e)&&(t.value=""+Le(e)):y!=="submit"&&y!=="reset"||t.removeAttribute("value"),e!=null?No(t,y,Le(e)):a!=null?No(t,y,Le(a)):s!=null&&t.removeAttribute("value"),o==null&&c!=null&&(t.defaultChecked=!!c),o!=null&&(t.checked=o&&typeof o!="function"&&typeof o!="symbol"),b!=null&&typeof b!="function"&&typeof b!="symbol"&&typeof b!="boolean"?t.name=""+Le(b):t.removeAttribute("name")}function Yd(t,e,a,s,o,c,y,b){if(c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(t.type=c),e!=null||a!=null){if(!(c!=="submit"&&c!=="reset"||e!=null)){Ro(t);return}a=a!=null?""+Le(a):"",e=e!=null?""+Le(e):a,b||e===t.value||(t.value=e),t.defaultValue=e}s=s??o,s=typeof s!="function"&&typeof s!="symbol"&&!!s,t.checked=b?t.checked:!!s,t.defaultChecked=!!s,y!=null&&typeof y!="function"&&typeof y!="symbol"&&typeof y!="boolean"&&(t.name=y),Ro(t)}function No(t,e,a){e==="number"&&pl(t.ownerDocument)===t||t.defaultValue===""+a||(t.defaultValue=""+a)}function $a(t,e,a,s){if(t=t.options,e){e={};for(var o=0;o"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),zo=!1;if(vn)try{var $i={};Object.defineProperty($i,"passive",{get:function(){zo=!0}}),window.addEventListener("test",$i,$i),window.removeEventListener("test",$i,$i)}catch{zo=!1}var Hn=null,Lo=null,gl=null;function $d(){if(gl)return gl;var t,e=Lo,a=e.length,s,o="value"in Hn?Hn.value:Hn.textContent,c=o.length;for(t=0;t=Wi),eh=" ",nh=!1;function ah(t,e){switch(t){case"keyup":return xb.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function ih(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Ia=!1;function Sb(t,e){switch(t){case"compositionend":return ih(e);case"keypress":return e.which!==32?null:(nh=!0,eh);case"textInput":return t=e.data,t===eh&&nh?null:t;default:return null}}function Tb(t,e){if(Ia)return t==="compositionend"||!Ho&&ah(t,e)?(t=$d(),gl=Lo=Hn=null,Ia=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1=e)return{node:a,offset:e-t};t=s}t:{for(;a;){if(a.nextSibling){a=a.nextSibling;break t}a=a.parentNode}a=void 0}a=dh(a)}}function mh(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?mh(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function ph(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=pl(t.document);e instanceof t.HTMLIFrameElement;){try{var a=typeof e.contentWindow.location.href=="string"}catch{a=!1}if(a)t=e.contentWindow;else break;e=pl(t.document)}return e}function qo(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var jb=vn&&"documentMode"in document&&11>=document.documentMode,ti=null,Po=null,ns=null,Xo=!1;function yh(t,e,a){var s=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;Xo||ti==null||ti!==pl(s)||(s=ti,"selectionStart"in s&&qo(s)?s={start:s.selectionStart,end:s.selectionEnd}:(s=(s.ownerDocument&&s.ownerDocument.defaultView||window).getSelection(),s={anchorNode:s.anchorNode,anchorOffset:s.anchorOffset,focusNode:s.focusNode,focusOffset:s.focusOffset}),ns&&es(ns,s)||(ns=s,s=cr(Po,"onSelect"),0>=y,o-=y,rn=1<<32-Ce(e)+o|a<mt?(bt=st,st=null):bt=st.sibling;var At=B(z,st,k[mt],P);if(At===null){st===null&&(st=bt);break}t&&st&&At.alternate===null&&e(z,st),j=c(At,j,mt),Et===null?rt=At:Et.sibling=At,Et=At,st=bt}if(mt===k.length)return a(z,st),St&&bn(z,mt),rt;if(st===null){for(;mtmt?(bt=st,st=null):bt=st.sibling;var oa=B(z,st,At.value,P);if(oa===null){st===null&&(st=bt);break}t&&st&&oa.alternate===null&&e(z,st),j=c(oa,j,mt),Et===null?rt=oa:Et.sibling=oa,Et=oa,st=bt}if(At.done)return a(z,st),St&&bn(z,mt),rt;if(st===null){for(;!At.done;mt++,At=k.next())At=X(z,At.value,P),At!==null&&(j=c(At,j,mt),Et===null?rt=At:Et.sibling=At,Et=At);return St&&bn(z,mt),rt}for(st=s(st);!At.done;mt++,At=k.next())At=G(st,z,mt,At.value,P),At!==null&&(t&&At.alternate!==null&&st.delete(At.key===null?mt:At.key),j=c(At,j,mt),Et===null?rt=At:Et.sibling=At,Et=At);return t&&st.forEach(function(F1){return e(z,F1)}),St&&bn(z,mt),rt}function Ot(z,j,k,P){if(typeof k=="object"&&k!==null&&k.type===w&&k.key===null&&(k=k.props.children),typeof k=="object"&&k!==null){switch(k.$$typeof){case E:t:{for(var rt=k.key;j!==null;){if(j.key===rt){if(rt=k.type,rt===w){if(j.tag===7){a(z,j.sibling),P=o(j,k.props.children),P.return=z,z=P;break t}}else if(j.elementType===rt||typeof rt=="object"&&rt!==null&&rt.$$typeof===q&&Na(rt)===j.type){a(z,j.sibling),P=o(j,k.props),os(P,k),P.return=z,z=P;break t}a(z,j);break}else e(z,j);j=j.sibling}k.type===w?(P=Aa(k.props.children,z.mode,P,k.key),P.return=z,z=P):(P=Rl(k.type,k.key,k.props,null,z.mode,P),os(P,k),P.return=z,z=P)}return y(z);case A:t:{for(rt=k.key;j!==null;){if(j.key===rt)if(j.tag===4&&j.stateNode.containerInfo===k.containerInfo&&j.stateNode.implementation===k.implementation){a(z,j.sibling),P=o(j,k.children||[]),P.return=z,z=P;break t}else{a(z,j);break}else e(z,j);j=j.sibling}P=Wo(k,z.mode,P),P.return=z,z=P}return y(z);case q:return k=Na(k),Ot(z,j,k,P)}if(Q(k))return nt(z,j,k,P);if(ut(k)){if(rt=ut(k),typeof rt!="function")throw Error(r(150));return k=rt.call(k),ot(z,j,k,P)}if(typeof k.then=="function")return Ot(z,j,zl(k),P);if(k.$$typeof===D)return Ot(z,j,jl(z,k),P);Ll(z,k)}return typeof k=="string"&&k!==""||typeof k=="number"||typeof k=="bigint"?(k=""+k,j!==null&&j.tag===6?(a(z,j.sibling),P=o(j,k),P.return=z,z=P):(a(z,j),P=Jo(k,z.mode,P),P.return=z,z=P),y(z)):a(z,j)}return function(z,j,k,P){try{rs=0;var rt=Ot(z,j,k,P);return fi=null,rt}catch(st){if(st===ci||st===Ml)throw st;var Et=Re(29,st,null,z.mode);return Et.lanes=P,Et.return=z,Et}finally{}}}var Da=Bh(!0),Hh=Bh(!1),Xn=!1;function cu(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function fu(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function Kn(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function Qn(t,e,a){var s=t.updateQueue;if(s===null)return null;if(s=s.shared,(Ct&2)!==0){var o=s.pending;return o===null?e.next=e:(e.next=o.next,o.next=e),s.pending=e,e=wl(t),Eh(t,null,a),e}return Cl(t,s,e,a),wl(t)}function us(t,e,a){if(e=e.updateQueue,e!==null&&(e=e.shared,(a&4194048)!==0)){var s=e.lanes;s&=t.pendingLanes,a|=s,e.lanes=a,Dd(t,a)}}function du(t,e){var a=t.updateQueue,s=t.alternate;if(s!==null&&(s=s.updateQueue,a===s)){var o=null,c=null;if(a=a.firstBaseUpdate,a!==null){do{var y={lane:a.lane,tag:a.tag,payload:a.payload,callback:null,next:null};c===null?o=c=y:c=c.next=y,a=a.next}while(a!==null);c===null?o=c=e:c=c.next=e}else o=c=e;a={baseState:s.baseState,firstBaseUpdate:o,lastBaseUpdate:c,shared:s.shared,callbacks:s.callbacks},t.updateQueue=a;return}t=a.lastBaseUpdate,t===null?a.firstBaseUpdate=e:t.next=e,a.lastBaseUpdate=e}var hu=!1;function cs(){if(hu){var t=ui;if(t!==null)throw t}}function fs(t,e,a,s){hu=!1;var o=t.updateQueue;Xn=!1;var c=o.firstBaseUpdate,y=o.lastBaseUpdate,b=o.shared.pending;if(b!==null){o.shared.pending=null;var C=b,U=C.next;C.next=null,y===null?c=U:y.next=U,y=C;var Y=t.alternate;Y!==null&&(Y=Y.updateQueue,b=Y.lastBaseUpdate,b!==y&&(b===null?Y.firstBaseUpdate=U:b.next=U,Y.lastBaseUpdate=C))}if(c!==null){var X=o.baseState;y=0,Y=U=C=null,b=c;do{var B=b.lane&-536870913,G=B!==b.lane;if(G?(xt&B)===B:(s&B)===B){B!==0&&B===oi&&(hu=!0),Y!==null&&(Y=Y.next={lane:0,tag:b.tag,payload:b.payload,callback:null,next:null});t:{var nt=t,ot=b;B=e;var Ot=a;switch(ot.tag){case 1:if(nt=ot.payload,typeof nt=="function"){X=nt.call(Ot,X,B);break t}X=nt;break t;case 3:nt.flags=nt.flags&-65537|128;case 0:if(nt=ot.payload,B=typeof nt=="function"?nt.call(Ot,X,B):nt,B==null)break t;X=x({},X,B);break t;case 2:Xn=!0}}B=b.callback,B!==null&&(t.flags|=64,G&&(t.flags|=8192),G=o.callbacks,G===null?o.callbacks=[B]:G.push(B))}else G={lane:B,tag:b.tag,payload:b.payload,callback:b.callback,next:null},Y===null?(U=Y=G,C=X):Y=Y.next=G,y|=B;if(b=b.next,b===null){if(b=o.shared.pending,b===null)break;G=b,b=G.next,G.next=null,o.lastBaseUpdate=G,o.shared.pending=null}}while(!0);Y===null&&(C=X),o.baseState=C,o.firstBaseUpdate=U,o.lastBaseUpdate=Y,c===null&&(o.shared.lanes=0),Wn|=y,t.lanes=y,t.memoizedState=X}}function Gh(t,e){if(typeof t!="function")throw Error(r(191,t));t.call(e)}function Yh(t,e){var a=t.callbacks;if(a!==null)for(t.callbacks=null,t=0;tc?c:8;var y=L.T,b={};L.T=b,Mu(t,!1,e,a);try{var C=o(),U=L.S;if(U!==null&&U(b,C),C!==null&&typeof C=="object"&&typeof C.then=="function"){var Y=Bb(C,s);ms(t,e,Y,Me(t))}else ms(t,e,s,Me(t))}catch(X){ms(t,e,{then:function(){},status:"rejected",reason:X},Me())}finally{J.p=c,y!==null&&b.types!==null&&(y.types=b.types),L.T=y}}function Xb(){}function ju(t,e,a,s){if(t.tag!==5)throw Error(r(476));var o=bm(t).queue;xm(t,o,e,tt,a===null?Xb:function(){return Sm(t),a(s)})}function bm(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:tt,baseState:tt,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:An,lastRenderedState:tt},next:null};var a={};return e.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:An,lastRenderedState:a},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function Sm(t){var e=bm(t);e.next===null&&(e=t.alternate.memoizedState),ms(t,e.next.queue,{},Me())}function Du(){return se(Ds)}function Tm(){return Qt().memoizedState}function Em(){return Qt().memoizedState}function Kb(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var a=Me();t=Kn(a);var s=Qn(e,t,a);s!==null&&(Te(s,e,a),us(s,e,a)),e={cache:lu()},t.payload=e;return}e=e.return}}function Qb(t,e,a){var s=Me();a={lane:s,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Xl(t)?Cm(e,a):(a=$o(t,e,a,s),a!==null&&(Te(a,t,s),wm(a,e,s)))}function Am(t,e,a){var s=Me();ms(t,e,a,s)}function ms(t,e,a,s){var o={lane:s,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null};if(Xl(t))Cm(e,o);else{var c=t.alternate;if(t.lanes===0&&(c===null||c.lanes===0)&&(c=e.lastRenderedReducer,c!==null))try{var y=e.lastRenderedState,b=c(y,a);if(o.hasEagerState=!0,o.eagerState=b,we(b,y))return Cl(t,e,o,0),zt===null&&Al(),!1}catch{}finally{}if(a=$o(t,e,o,s),a!==null)return Te(a,t,s),wm(a,e,s),!0}return!1}function Mu(t,e,a,s){if(s={lane:2,revertLane:cc(),gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null},Xl(t)){if(e)throw Error(r(479))}else e=$o(t,a,s,2),e!==null&&Te(e,t,2)}function Xl(t){var e=t.alternate;return t===ht||e!==null&&e===ht}function Cm(t,e){hi=Vl=!0;var a=t.pending;a===null?e.next=e:(e.next=a.next,a.next=e),t.pending=e}function wm(t,e,a){if((a&4194048)!==0){var s=e.lanes;s&=t.pendingLanes,a|=s,e.lanes=a,Dd(t,a)}}var ps={readContext:se,use:Gl,useCallback:Yt,useContext:Yt,useEffect:Yt,useImperativeHandle:Yt,useLayoutEffect:Yt,useInsertionEffect:Yt,useMemo:Yt,useReducer:Yt,useRef:Yt,useState:Yt,useDebugValue:Yt,useDeferredValue:Yt,useTransition:Yt,useSyncExternalStore:Yt,useId:Yt,useHostTransitionStatus:Yt,useFormState:Yt,useActionState:Yt,useOptimistic:Yt,useMemoCache:Yt,useCacheRefresh:Yt};ps.useEffectEvent=Yt;var Rm={readContext:se,use:Gl,useCallback:function(t,e){return de().memoizedState=[t,e===void 0?null:e],t},useContext:se,useEffect:cm,useImperativeHandle:function(t,e,a){a=a!=null?a.concat([t]):null,ql(4194308,4,mm.bind(null,e,t),a)},useLayoutEffect:function(t,e){return ql(4194308,4,t,e)},useInsertionEffect:function(t,e){ql(4,2,t,e)},useMemo:function(t,e){var a=de();e=e===void 0?null:e;var s=t();if(Ma){Vn(!0);try{t()}finally{Vn(!1)}}return a.memoizedState=[s,e],s},useReducer:function(t,e,a){var s=de();if(a!==void 0){var o=a(e);if(Ma){Vn(!0);try{a(e)}finally{Vn(!1)}}}else o=e;return s.memoizedState=s.baseState=o,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:o},s.queue=t,t=t.dispatch=Qb.bind(null,ht,t),[s.memoizedState,t]},useRef:function(t){var e=de();return t={current:t},e.memoizedState=t},useState:function(t){t=Cu(t);var e=t.queue,a=Am.bind(null,ht,e);return e.dispatch=a,[t.memoizedState,a]},useDebugValue:_u,useDeferredValue:function(t,e){var a=de();return Nu(a,t,e)},useTransition:function(){var t=Cu(!1);return t=xm.bind(null,ht,t.queue,!0,!1),de().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,a){var s=ht,o=de();if(St){if(a===void 0)throw Error(r(407));a=a()}else{if(a=e(),zt===null)throw Error(r(349));(xt&127)!==0||Zh(s,e,a)}o.memoizedState=a;var c={value:a,getSnapshot:e};return o.queue=c,cm(Fh.bind(null,s,c,t),[t]),s.flags|=2048,pi(9,{destroy:void 0},$h.bind(null,s,c,a,e),null),a},useId:function(){var t=de(),e=zt.identifierPrefix;if(St){var a=on,s=rn;a=(s&~(1<<32-Ce(s)-1)).toString(32)+a,e="_"+e+"R_"+a,a=Bl++,0<\/script>",c=c.removeChild(c.firstChild);break;case"select":c=typeof s.is=="string"?y.createElement("select",{is:s.is}):y.createElement("select"),s.multiple?c.multiple=!0:s.size&&(c.size=s.size);break;default:c=typeof s.is=="string"?y.createElement(o,{is:s.is}):y.createElement(o)}}c[ae]=e,c[ye]=s;t:for(y=e.child;y!==null;){if(y.tag===5||y.tag===6)c.appendChild(y.stateNode);else if(y.tag!==4&&y.tag!==27&&y.child!==null){y.child.return=y,y=y.child;continue}if(y===e)break t;for(;y.sibling===null;){if(y.return===null||y.return===e)break t;y=y.return}y.sibling.return=y.return,y=y.sibling}e.stateNode=c;t:switch(re(c,o,s),o){case"button":case"input":case"select":case"textarea":s=!!s.autoFocus;break t;case"img":s=!0;break t;default:s=!1}s&&wn(e)}}return Bt(e),Ku(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,a),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==s&&wn(e);else{if(typeof s!="string"&&e.stateNode===null)throw Error(r(166));if(t=yt.current,li(e)){if(t=e.stateNode,a=e.memoizedProps,s=null,o=ie,o!==null)switch(o.tag){case 27:case 5:s=o.memoizedProps}t[ae]=e,t=!!(t.nodeValue===a||s!==null&&s.suppressHydrationWarning===!0||Kp(t.nodeValue,a)),t||qn(e,!0)}else t=fr(t).createTextNode(s),t[ae]=e,e.stateNode=t}return Bt(e),null;case 31:if(a=e.memoizedState,t===null||t.memoizedState!==null){if(s=li(e),a!==null){if(t===null){if(!s)throw Error(r(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[ae]=e}else Ca(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Bt(e),t=!1}else a=nu(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=a),t=!0;if(!t)return e.flags&256?(Ne(e),e):(Ne(e),null);if((e.flags&128)!==0)throw Error(r(558))}return Bt(e),null;case 13:if(s=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(o=li(e),s!==null&&s.dehydrated!==null){if(t===null){if(!o)throw Error(r(318));if(o=e.memoizedState,o=o!==null?o.dehydrated:null,!o)throw Error(r(317));o[ae]=e}else Ca(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Bt(e),o=!1}else o=nu(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=o),o=!0;if(!o)return e.flags&256?(Ne(e),e):(Ne(e),null)}return Ne(e),(e.flags&128)!==0?(e.lanes=a,e):(a=s!==null,t=t!==null&&t.memoizedState!==null,a&&(s=e.child,o=null,s.alternate!==null&&s.alternate.memoizedState!==null&&s.alternate.memoizedState.cachePool!==null&&(o=s.alternate.memoizedState.cachePool.pool),c=null,s.memoizedState!==null&&s.memoizedState.cachePool!==null&&(c=s.memoizedState.cachePool.pool),c!==o&&(s.flags|=2048)),a!==t&&a&&(e.child.flags|=8192),Fl(e,e.updateQueue),Bt(e),null);case 4:return Xt(),t===null&&mc(e.stateNode.containerInfo),Bt(e),null;case 10:return Tn(e.type),Bt(e),null;case 19:if(K(Kt),s=e.memoizedState,s===null)return Bt(e),null;if(o=(e.flags&128)!==0,c=s.rendering,c===null)if(o)gs(s,!1);else{if(qt!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(c=Ul(t),c!==null){for(e.flags|=128,gs(s,!1),t=c.updateQueue,e.updateQueue=t,Fl(e,t),e.subtreeFlags=0,t=a,a=e.child;a!==null;)Ah(a,t),a=a.sibling;return et(Kt,Kt.current&1|2),St&&bn(e,s.treeForkCount),e.child}t=t.sibling}s.tail!==null&&Ee()>er&&(e.flags|=128,o=!0,gs(s,!1),e.lanes=4194304)}else{if(!o)if(t=Ul(c),t!==null){if(e.flags|=128,o=!0,t=t.updateQueue,e.updateQueue=t,Fl(e,t),gs(s,!0),s.tail===null&&s.tailMode==="hidden"&&!c.alternate&&!St)return Bt(e),null}else 2*Ee()-s.renderingStartTime>er&&a!==536870912&&(e.flags|=128,o=!0,gs(s,!1),e.lanes=4194304);s.isBackwards?(c.sibling=e.child,e.child=c):(t=s.last,t!==null?t.sibling=c:e.child=c,s.last=c)}return s.tail!==null?(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=Ee(),t.sibling=null,a=Kt.current,et(Kt,o?a&1|2:a&1),St&&bn(e,s.treeForkCount),t):(Bt(e),null);case 22:case 23:return Ne(e),pu(),s=e.memoizedState!==null,t!==null?t.memoizedState!==null!==s&&(e.flags|=8192):s&&(e.flags|=8192),s?(a&536870912)!==0&&(e.flags&128)===0&&(Bt(e),e.subtreeFlags&6&&(e.flags|=8192)):Bt(e),a=e.updateQueue,a!==null&&Fl(e,a.retryQueue),a=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),s=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(s=e.memoizedState.cachePool.pool),s!==a&&(e.flags|=2048),t!==null&&K(_a),null;case 24:return a=null,t!==null&&(a=t.memoizedState.cache),e.memoizedState.cache!==a&&(e.flags|=2048),Tn(Zt),Bt(e),null;case 25:return null;case 30:return null}throw Error(r(156,e.tag))}function Wb(t,e){switch(tu(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return Tn(Zt),Xt(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return rl(e),null;case 31:if(e.memoizedState!==null){if(Ne(e),e.alternate===null)throw Error(r(340));Ca()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(Ne(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(r(340));Ca()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return K(Kt),null;case 4:return Xt(),null;case 10:return Tn(e.type),null;case 22:case 23:return Ne(e),pu(),t!==null&&K(_a),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return Tn(Zt),null;case 25:return null;default:return null}}function Jm(t,e){switch(tu(e),e.tag){case 3:Tn(Zt),Xt();break;case 26:case 27:case 5:rl(e);break;case 4:Xt();break;case 31:e.memoizedState!==null&&Ne(e);break;case 13:Ne(e);break;case 19:K(Kt);break;case 10:Tn(e.type);break;case 22:case 23:Ne(e),pu(),t!==null&&K(_a);break;case 24:Tn(Zt)}}function vs(t,e){try{var a=e.updateQueue,s=a!==null?a.lastEffect:null;if(s!==null){var o=s.next;a=o;do{if((a.tag&t)===t){s=void 0;var c=a.create,y=a.inst;s=c(),y.destroy=s}a=a.next}while(a!==o)}}catch(b){jt(e,e.return,b)}}function Fn(t,e,a){try{var s=e.updateQueue,o=s!==null?s.lastEffect:null;if(o!==null){var c=o.next;s=c;do{if((s.tag&t)===t){var y=s.inst,b=y.destroy;if(b!==void 0){y.destroy=void 0,o=e;var C=a,U=b;try{U()}catch(Y){jt(o,C,Y)}}}s=s.next}while(s!==c)}}catch(Y){jt(e,e.return,Y)}}function Wm(t){var e=t.updateQueue;if(e!==null){var a=t.stateNode;try{Yh(e,a)}catch(s){jt(t,t.return,s)}}}function Im(t,e,a){a.props=Oa(t.type,t.memoizedProps),a.state=t.memoizedState;try{a.componentWillUnmount()}catch(s){jt(t,e,s)}}function xs(t,e){try{var a=t.ref;if(a!==null){switch(t.tag){case 26:case 27:case 5:var s=t.stateNode;break;case 30:s=t.stateNode;break;default:s=t.stateNode}typeof a=="function"?t.refCleanup=a(s):a.current=s}}catch(o){jt(t,e,o)}}function un(t,e){var a=t.ref,s=t.refCleanup;if(a!==null)if(typeof s=="function")try{s()}catch(o){jt(t,e,o)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof a=="function")try{a(null)}catch(o){jt(t,e,o)}else a.current=null}function tp(t){var e=t.type,a=t.memoizedProps,s=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":a.autoFocus&&s.focus();break t;case"img":a.src?s.src=a.src:a.srcSet&&(s.srcset=a.srcSet)}}catch(o){jt(t,t.return,o)}}function Qu(t,e,a){try{var s=t.stateNode;b1(s,t.type,a,e),s[ye]=e}catch(o){jt(t,t.return,o)}}function ep(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&aa(t.type)||t.tag===4}function Zu(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||ep(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&aa(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function $u(t,e,a){var s=t.tag;if(s===5||s===6)t=t.stateNode,e?(a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a).insertBefore(t,e):(e=a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a,e.appendChild(t),a=a._reactRootContainer,a!=null||e.onclick!==null||(e.onclick=gn));else if(s!==4&&(s===27&&aa(t.type)&&(a=t.stateNode,e=null),t=t.child,t!==null))for($u(t,e,a),t=t.sibling;t!==null;)$u(t,e,a),t=t.sibling}function Jl(t,e,a){var s=t.tag;if(s===5||s===6)t=t.stateNode,e?a.insertBefore(t,e):a.appendChild(t);else if(s!==4&&(s===27&&aa(t.type)&&(a=t.stateNode),t=t.child,t!==null))for(Jl(t,e,a),t=t.sibling;t!==null;)Jl(t,e,a),t=t.sibling}function np(t){var e=t.stateNode,a=t.memoizedProps;try{for(var s=t.type,o=e.attributes;o.length;)e.removeAttributeNode(o[0]);re(e,s,a),e[ae]=t,e[ye]=a}catch(c){jt(t,t.return,c)}}var Rn=!1,Jt=!1,Fu=!1,ap=typeof WeakSet=="function"?WeakSet:Set,ne=null;function Ib(t,e){if(t=t.containerInfo,gc=vr,t=ph(t),qo(t)){if("selectionStart"in t)var a={start:t.selectionStart,end:t.selectionEnd};else t:{a=(a=t.ownerDocument)&&a.defaultView||window;var s=a.getSelection&&a.getSelection();if(s&&s.rangeCount!==0){a=s.anchorNode;var o=s.anchorOffset,c=s.focusNode;s=s.focusOffset;try{a.nodeType,c.nodeType}catch{a=null;break t}var y=0,b=-1,C=-1,U=0,Y=0,X=t,B=null;e:for(;;){for(var G;X!==a||o!==0&&X.nodeType!==3||(b=y+o),X!==c||s!==0&&X.nodeType!==3||(C=y+s),X.nodeType===3&&(y+=X.nodeValue.length),(G=X.firstChild)!==null;)B=X,X=G;for(;;){if(X===t)break e;if(B===a&&++U===o&&(b=y),B===c&&++Y===s&&(C=y),(G=X.nextSibling)!==null)break;X=B,B=X.parentNode}X=G}a=b===-1||C===-1?null:{start:b,end:C}}else a=null}a=a||{start:0,end:0}}else a=null;for(vc={focusedElem:t,selectionRange:a},vr=!1,ne=e;ne!==null;)if(e=ne,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,ne=t;else for(;ne!==null;){switch(e=ne,c=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(a=0;a title"))),re(c,s,a),c[ae]=t,ee(c),s=c;break t;case"link":var y=uy("link","href",o).get(s+(a.href||""));if(y){for(var b=0;bOt&&(y=Ot,Ot=ot,ot=y);var z=hh(b,ot),j=hh(b,Ot);if(z&&j&&(G.rangeCount!==1||G.anchorNode!==z.node||G.anchorOffset!==z.offset||G.focusNode!==j.node||G.focusOffset!==j.offset)){var k=X.createRange();k.setStart(z.node,z.offset),G.removeAllRanges(),ot>Ot?(G.addRange(k),G.extend(j.node,j.offset)):(k.setEnd(j.node,j.offset),G.addRange(k))}}}}for(X=[],G=b;G=G.parentNode;)G.nodeType===1&&X.push({element:G,left:G.scrollLeft,top:G.scrollTop});for(typeof b.focus=="function"&&b.focus(),b=0;ba?32:a,L.T=null,a=ac,ac=null;var c=ta,y=Mn;if(It=0,bi=ta=null,Mn=0,(Ct&6)!==0)throw Error(r(331));var b=Ct;if(Ct|=4,mp(c.current),fp(c,c.current,y,a),Ct=b,Cs(0,!1),Ae&&typeof Ae.onPostCommitFiberRoot=="function")try{Ae.onPostCommitFiberRoot(qi,c)}catch{}return!0}finally{J.p=o,L.T=s,Mp(t,e)}}function zp(t,e,a){e=Ve(a,e),e=ku(t.stateNode,e,2),t=Qn(t,e,2),t!==null&&(Xi(t,2),cn(t))}function jt(t,e,a){if(t.tag===3)zp(t,t,a);else for(;e!==null;){if(e.tag===3){zp(e,t,a);break}else if(e.tag===1){var s=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof s.componentDidCatch=="function"&&(In===null||!In.has(s))){t=Ve(a,t),a=Lm(2),s=Qn(e,a,2),s!==null&&(km(a,s,e,t),Xi(s,2),cn(s));break}}e=e.return}}function rc(t,e,a){var s=t.pingCache;if(s===null){s=t.pingCache=new n1;var o=new Set;s.set(e,o)}else o=s.get(e),o===void 0&&(o=new Set,s.set(e,o));o.has(a)||(Iu=!0,o.add(a),t=r1.bind(null,t,e,a),e.then(t,t))}function r1(t,e,a){var s=t.pingCache;s!==null&&s.delete(e),t.pingedLanes|=t.suspendedLanes&a,t.warmLanes&=~a,zt===t&&(xt&a)===a&&(qt===4||qt===3&&(xt&62914560)===xt&&300>Ee()-tr?(Ct&2)===0&&Si(t,0):tc|=a,xi===xt&&(xi=0)),cn(t)}function Lp(t,e){e===0&&(e=Nd()),t=Ea(t,e),t!==null&&(Xi(t,e),cn(t))}function o1(t){var e=t.memoizedState,a=0;e!==null&&(a=e.retryLane),Lp(t,a)}function u1(t,e){var a=0;switch(t.tag){case 31:case 13:var s=t.stateNode,o=t.memoizedState;o!==null&&(a=o.retryLane);break;case 19:s=t.stateNode;break;case 22:s=t.stateNode._retryCache;break;default:throw Error(r(314))}s!==null&&s.delete(e),Lp(t,a)}function c1(t,e){return bo(t,e)}var rr=null,Ei=null,oc=!1,or=!1,uc=!1,na=0;function cn(t){t!==Ei&&t.next===null&&(Ei===null?rr=Ei=t:Ei=Ei.next=t),or=!0,oc||(oc=!0,d1())}function Cs(t,e){if(!uc&&or){uc=!0;do for(var a=!1,s=rr;s!==null;){if(t!==0){var o=s.pendingLanes;if(o===0)var c=0;else{var y=s.suspendedLanes,b=s.pingedLanes;c=(1<<31-Ce(42|t)+1)-1,c&=o&~(y&~b),c=c&201326741?c&201326741|1:c?c|2:0}c!==0&&(a=!0,Bp(s,c))}else c=xt,c=dl(s,s===zt?c:0,s.cancelPendingCommit!==null||s.timeoutHandle!==-1),(c&3)===0||Pi(s,c)||(a=!0,Bp(s,c));s=s.next}while(a);uc=!1}}function f1(){kp()}function kp(){or=oc=!1;var t=0;na!==0&&T1()&&(t=na);for(var e=Ee(),a=null,s=rr;s!==null;){var o=s.next,c=Up(s,e);c===0?(s.next=null,a===null?rr=o:a.next=o,o===null&&(Ei=a)):(a=s,(t!==0||(c&3)!==0)&&(or=!0)),s=o}It!==0&&It!==5||Cs(t),na!==0&&(na=0)}function Up(t,e){for(var a=t.suspendedLanes,s=t.pingedLanes,o=t.expirationTimes,c=t.pendingLanes&-62914561;0b)break;var Y=C.transferSize,X=C.initiatorType;Y&&Qp(X)&&(C=C.responseEnd,y+=Y*(C"u"?null:document;function sy(t,e,a){var s=Ai;if(s&&typeof e=="string"&&e){var o=ke(e);o='link[rel="'+t+'"][href="'+o+'"]',typeof a=="string"&&(o+='[crossorigin="'+a+'"]'),iy.has(o)||(iy.add(o),t={rel:t,crossOrigin:a,href:e},s.querySelector(o)===null&&(e=s.createElement("link"),re(e,"link",t),ee(e),s.head.appendChild(e)))}}function D1(t){On.D(t),sy("dns-prefetch",t,null)}function M1(t,e){On.C(t,e),sy("preconnect",t,e)}function O1(t,e,a){On.L(t,e,a);var s=Ai;if(s&&t&&e){var o='link[rel="preload"][as="'+ke(e)+'"]';e==="image"&&a&&a.imageSrcSet?(o+='[imagesrcset="'+ke(a.imageSrcSet)+'"]',typeof a.imageSizes=="string"&&(o+='[imagesizes="'+ke(a.imageSizes)+'"]')):o+='[href="'+ke(t)+'"]';var c=o;switch(e){case"style":c=Ci(t);break;case"script":c=wi(t)}Pe.has(c)||(t=x({rel:"preload",href:e==="image"&&a&&a.imageSrcSet?void 0:t,as:e},a),Pe.set(c,t),s.querySelector(o)!==null||e==="style"&&s.querySelector(Ns(c))||e==="script"&&s.querySelector(js(c))||(e=s.createElement("link"),re(e,"link",t),ee(e),s.head.appendChild(e)))}}function z1(t,e){On.m(t,e);var a=Ai;if(a&&t){var s=e&&typeof e.as=="string"?e.as:"script",o='link[rel="modulepreload"][as="'+ke(s)+'"][href="'+ke(t)+'"]',c=o;switch(s){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":c=wi(t)}if(!Pe.has(c)&&(t=x({rel:"modulepreload",href:t},e),Pe.set(c,t),a.querySelector(o)===null)){switch(s){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(a.querySelector(js(c)))return}s=a.createElement("link"),re(s,"link",t),ee(s),a.head.appendChild(s)}}}function L1(t,e,a){On.S(t,e,a);var s=Ai;if(s&&t){var o=Qa(s).hoistableStyles,c=Ci(t);e=e||"default";var y=o.get(c);if(!y){var b={loading:0,preload:null};if(y=s.querySelector(Ns(c)))b.loading=5;else{t=x({rel:"stylesheet",href:t,"data-precedence":e},a),(a=Pe.get(c))&&Cc(t,a);var C=y=s.createElement("link");ee(C),re(C,"link",t),C._p=new Promise(function(U,Y){C.onload=U,C.onerror=Y}),C.addEventListener("load",function(){b.loading|=1}),C.addEventListener("error",function(){b.loading|=2}),b.loading|=4,hr(y,e,s)}y={type:"stylesheet",instance:y,count:1,state:b},o.set(c,y)}}}function k1(t,e){On.X(t,e);var a=Ai;if(a&&t){var s=Qa(a).hoistableScripts,o=wi(t),c=s.get(o);c||(c=a.querySelector(js(o)),c||(t=x({src:t,async:!0},e),(e=Pe.get(o))&&wc(t,e),c=a.createElement("script"),ee(c),re(c,"link",t),a.head.appendChild(c)),c={type:"script",instance:c,count:1,state:null},s.set(o,c))}}function U1(t,e){On.M(t,e);var a=Ai;if(a&&t){var s=Qa(a).hoistableScripts,o=wi(t),c=s.get(o);c||(c=a.querySelector(js(o)),c||(t=x({src:t,async:!0,type:"module"},e),(e=Pe.get(o))&&wc(t,e),c=a.createElement("script"),ee(c),re(c,"link",t),a.head.appendChild(c)),c={type:"script",instance:c,count:1,state:null},s.set(o,c))}}function ly(t,e,a,s){var o=(o=yt.current)?dr(o):null;if(!o)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof a.precedence=="string"&&typeof a.href=="string"?(e=Ci(a.href),a=Qa(o).hoistableStyles,s=a.get(e),s||(s={type:"style",instance:null,count:0,state:null},a.set(e,s)),s):{type:"void",instance:null,count:0,state:null};case"link":if(a.rel==="stylesheet"&&typeof a.href=="string"&&typeof a.precedence=="string"){t=Ci(a.href);var c=Qa(o).hoistableStyles,y=c.get(t);if(y||(o=o.ownerDocument||o,y={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},c.set(t,y),(c=o.querySelector(Ns(t)))&&!c._p&&(y.instance=c,y.state.loading=5),Pe.has(t)||(a={rel:"preload",as:"style",href:a.href,crossOrigin:a.crossOrigin,integrity:a.integrity,media:a.media,hrefLang:a.hrefLang,referrerPolicy:a.referrerPolicy},Pe.set(t,a),c||V1(o,t,a,y.state))),e&&s===null)throw Error(r(528,""));return y}if(e&&s!==null)throw Error(r(529,""));return null;case"script":return e=a.async,a=a.src,typeof a=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=wi(a),a=Qa(o).hoistableScripts,s=a.get(e),s||(s={type:"script",instance:null,count:0,state:null},a.set(e,s)),s):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function Ci(t){return'href="'+ke(t)+'"'}function Ns(t){return'link[rel="stylesheet"]['+t+"]"}function ry(t){return x({},t,{"data-precedence":t.precedence,precedence:null})}function V1(t,e,a,s){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?s.loading=1:(e=t.createElement("link"),s.preload=e,e.addEventListener("load",function(){return s.loading|=1}),e.addEventListener("error",function(){return s.loading|=2}),re(e,"link",a),ee(e),t.head.appendChild(e))}function wi(t){return'[src="'+ke(t)+'"]'}function js(t){return"script[async]"+t}function oy(t,e,a){if(e.count++,e.instance===null)switch(e.type){case"style":var s=t.querySelector('style[data-href~="'+ke(a.href)+'"]');if(s)return e.instance=s,ee(s),s;var o=x({},a,{"data-href":a.href,"data-precedence":a.precedence,href:null,precedence:null});return s=(t.ownerDocument||t).createElement("style"),ee(s),re(s,"style",o),hr(s,a.precedence,t),e.instance=s;case"stylesheet":o=Ci(a.href);var c=t.querySelector(Ns(o));if(c)return e.state.loading|=4,e.instance=c,ee(c),c;s=ry(a),(o=Pe.get(o))&&Cc(s,o),c=(t.ownerDocument||t).createElement("link"),ee(c);var y=c;return y._p=new Promise(function(b,C){y.onload=b,y.onerror=C}),re(c,"link",s),e.state.loading|=4,hr(c,a.precedence,t),e.instance=c;case"script":return c=wi(a.src),(o=t.querySelector(js(c)))?(e.instance=o,ee(o),o):(s=a,(o=Pe.get(c))&&(s=x({},a),wc(s,o)),t=t.ownerDocument||t,o=t.createElement("script"),ee(o),re(o,"link",s),t.head.appendChild(o),e.instance=o);case"void":return null;default:throw Error(r(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(s=e.instance,e.state.loading|=4,hr(s,a.precedence,t));return e.instance}function hr(t,e,a){for(var s=a.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),o=s.length?s[s.length-1]:null,c=o,y=0;y title"):null)}function B1(t,e,a){if(a===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;switch(e.rel){case"stylesheet":return t=e.disabled,typeof e.precedence=="string"&&t==null;default:return!0}case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function fy(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function H1(t,e,a,s){if(a.type==="stylesheet"&&(typeof s.media!="string"||matchMedia(s.media).matches!==!1)&&(a.state.loading&4)===0){if(a.instance===null){var o=Ci(s.href),c=e.querySelector(Ns(o));if(c){e=c._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=pr.bind(t),e.then(t,t)),a.state.loading|=4,a.instance=c,ee(c);return}c=e.ownerDocument||e,s=ry(s),(o=Pe.get(o))&&Cc(s,o),c=c.createElement("link"),ee(c);var y=c;y._p=new Promise(function(b,C){y.onload=b,y.onerror=C}),re(c,"link",s),a.instance=c}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(a,e),(e=a.state.preload)&&(a.state.loading&3)===0&&(t.count++,a=pr.bind(t),e.addEventListener("load",a),e.addEventListener("error",a))}}var Rc=0;function G1(t,e){return t.stylesheets&&t.count===0&&gr(t,t.stylesheets),0Rc?50:800)+e);return t.unsuspend=a,function(){t.unsuspend=null,clearTimeout(s),clearTimeout(o)}}:null}function pr(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)gr(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var yr=null;function gr(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,yr=new Map,e.forEach(Y1,t),yr=null,pr.call(t))}function Y1(t,e){if(!(e.state.loading&4)){var a=yr.get(t);if(a)var s=a.get(null);else{a=new Map,yr.set(t,a);for(var o=t.querySelectorAll("link[data-precedence],style[data-precedence]"),c=0;c"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(i){console.error(i)}}return n(),kc.exports=iS(),kc.exports}var lS=sS();const rS=mg(lS);/** + * react-router v7.18.1 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */var Bf=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,pg=/^[\\/]{2}/;function oS(n,i){return i+n.replace(/\\/g,"/")}var Ly="popstate";function ky(n){return typeof n=="object"&&n!=null&&"pathname"in n&&"search"in n&&"hash"in n&&"state"in n&&"key"in n}function uS(n={}){function i(r,u){var p;let d=(p=u.state)==null?void 0:p.masked,{pathname:f,search:h,hash:m}=d||r.location;return rf("",{pathname:f,search:h,hash:m},u.state&&u.state.usr||null,u.state&&u.state.key||"default",d?{pathname:r.location.pathname,search:r.location.search,hash:r.location.hash}:void 0)}function l(r,u){return typeof u=="string"?u:Xs(u)}return fS(i,l,null,n)}function Gt(n,i){if(n===!1||n===null||typeof n>"u")throw new Error(i)}function an(n,i){if(!n){typeof console<"u"&&console.warn(i);try{throw new Error(i)}catch{}}}function cS(){return Math.random().toString(36).substring(2,10)}function Uy(n,i){return{usr:n.state,key:n.key,idx:i,masked:n.mask?{pathname:n.pathname,search:n.search,hash:n.hash}:void 0}}function rf(n,i,l=null,r,u){return{pathname:typeof n=="string"?n:n.pathname,search:"",hash:"",...typeof i=="string"?Li(i):i,state:l,key:i&&i.key||r||cS(),mask:u}}function Xs({pathname:n="/",search:i="",hash:l=""}){return i&&i!=="?"&&(n+=i.charAt(0)==="?"?i:"?"+i),l&&l!=="#"&&(n+=l.charAt(0)==="#"?l:"#"+l),n}function Li(n){let i={};if(n){let l=n.indexOf("#");l>=0&&(i.hash=n.substring(l),n=n.substring(0,l));let r=n.indexOf("?");r>=0&&(i.search=n.substring(r),n=n.substring(0,r)),n&&(i.pathname=n)}return i}function fS(n,i,l,r={}){let{window:u=document.defaultView,v5Compat:d=!1}=r,f=u.history,h="POP",m=null,p=v();p==null&&(p=0,f.replaceState({...f.state,idx:p},""));function v(){return(f.state||{idx:null}).idx}function x(){h="POP";let _=v(),R=_==null?null:_-p;p=_,m&&m({action:h,location:w.location,delta:R})}function S(_,R){h="PUSH";let O=ky(_)?_:rf(w.location,_,R);p=v()+1;let D=Uy(O,p),M=w.createHref(O.mask||O);try{f.pushState(D,"",M)}catch(H){if(H instanceof DOMException&&H.name==="DataCloneError")throw H;u.location.assign(M)}d&&m&&m({action:h,location:w.location,delta:1})}function E(_,R){h="REPLACE";let O=ky(_)?_:rf(w.location,_,R);p=v();let D=Uy(O,p),M=w.createHref(O.mask||O);f.replaceState(D,"",M),d&&m&&m({action:h,location:w.location,delta:0})}function A(_){return dS(u,_)}let w={get action(){return h},get location(){return n(u,f)},listen(_){if(m)throw new Error("A history only accepts one active listener");return u.addEventListener(Ly,x),m=_,()=>{u.removeEventListener(Ly,x),m=null}},createHref(_){return i(u,_)},createURL:A,encodeLocation(_){let R=A(_);return{pathname:R.pathname,search:R.search,hash:R.hash}},push:S,replace:E,go(_){return f.go(_)}};return w}function dS(n,i,l=!1){let r="http://localhost";n&&(r=n.location.origin!=="null"?n.location.origin:n.location.href),Gt(r,"No window.location.(origin|href) available to create URL");let u=typeof i=="string"?i:Xs(i);return u=u.replace(/ $/,"%20"),!l&&pg.test(u)&&(u=r+u),new URL(u,r)}function yg(n,i,l="/"){return hS(n,i,l,!1)}function hS(n,i,l,r,u){let d=typeof i=="string"?Li(i):i,f=kn(d.pathname||"/",l);if(f==null)return null;let h=mS(n),m=null,p=CS(f);for(let v=0;m==null&&v{let v={relativePath:p===void 0?f.path||"":p,caseSensitive:f.caseSensitive===!0,childrenIndex:h,route:f};if(v.relativePath.startsWith("/")){if(!v.relativePath.startsWith(r)&&m)return;Gt(v.relativePath.startsWith(r),`Absolute route path "${v.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),v.relativePath=v.relativePath.slice(r.length)}let x=tn([r,v.relativePath]),S=l.concat(v);f.children&&f.children.length>0&&(Gt(f.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${x}".`),gg(f.children,i,S,x,m)),!(f.path==null&&!f.index)&&i.push({path:x,score:TS(x,f.index),routesMeta:S.map((E,A)=>{let[w,_]=bg(E.relativePath,E.caseSensitive,A===S.length-1);return{...E,matcher:w,compiledParams:_}})})};return n.forEach((f,h)=>{var m;if(f.path===""||!((m=f.path)!=null&&m.includes("?")))d(f,h);else for(let p of vg(f.path))d(f,h,!0,p)}),i}function vg(n){let i=n.split("/");if(i.length===0)return[];let[l,...r]=i,u=l.endsWith("?"),d=l.replace(/\?$/,"");if(r.length===0)return u?[d,""]:[d];let f=vg(r.join("/")),h=[];return h.push(...f.map(m=>m===""?d:[d,m].join("/"))),u&&h.push(...f),h.map(m=>n.startsWith("/")&&m===""?"/":m)}function pS(n){n.sort((i,l)=>i.score!==l.score?l.score-i.score:ES(i.routesMeta.map(r=>r.childrenIndex),l.routesMeta.map(r=>r.childrenIndex)))}var yS=/^:[\w-]+$/,gS=3,vS=2,xS=1,bS=10,SS=-2,Vy=n=>n==="*";function TS(n,i){let l=n.split("/"),r=l.length;return l.some(Vy)&&(r+=SS),i&&(r+=vS),l.filter(u=>!Vy(u)).reduce((u,d)=>u+(yS.test(d)?gS:d===""?xS:bS),r)}function ES(n,i){return n.length===i.length&&n.slice(0,-1).every((r,u)=>r===i[u])?n[n.length-1]-i[i.length-1]:0}function AS(n,i,l=!1){let{routesMeta:r}=n,u={},d="/",f=[];for(let h=0;h{if(v==="*"){let A=h[S]||"";f=d.slice(0,d.length-A.length).replace(/(.)\/+$/,"$1")}const E=h[S];return x&&!E?p[v]=void 0:p[v]=(E||"").replace(/%2F/g,"/"),p},{}),pathname:d,pathnameBase:f,pattern:n}}function bg(n,i=!1,l=!0){an(n==="*"||!n.endsWith("*")||n.endsWith("/*"),`Route path "${n}" will be treated as if it were "${n.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${n.replace(/\*$/,"/*")}".`);let r=[],u="^"+n.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(f,h,m,p,v)=>{if(r.push({paramName:h,isOptional:m!=null}),m){let x=v.charAt(p+f.length);return x&&x!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return n.endsWith("*")?(r.push({paramName:"*"}),u+=n==="*"||n==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):l?u+="\\/*$":n!==""&&n!=="/"&&(u+="(?:(?=\\/|$))"),[new RegExp(u,i?void 0:"i"),r]}function CS(n){try{return n.split("/").map(i=>decodeURIComponent(i).replace(/\//g,"%2F")).join("/")}catch(i){return an(!1,`The URL path "${n}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${i}).`),n}}function kn(n,i){if(i==="/")return n;if(!n.toLowerCase().startsWith(i.toLowerCase()))return null;let l=i.endsWith("/")?i.length-1:i.length,r=n.charAt(l);return r&&r!=="/"?null:n.slice(l)||"/"}function wS(n,i="/"){let{pathname:l,search:r="",hash:u=""}=typeof n=="string"?Li(n):n,d;return l?(l=Sg(l),l.startsWith("/")?d=By(l.substring(1),"/"):d=By(l,i)):d=i,{pathname:d,search:NS(r),hash:jS(u)}}function By(n,i){let l=Xr(i).split("/");return n.split("/").forEach(u=>{u===".."?l.length>1&&l.pop():u!=="."&&l.push(u)}),l.length>1?l.join("/"):"/"}function Hc(n,i,l,r){return`Cannot include a '${n}' character in a manually specified \`to.${i}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${l}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function RS(n){return n.filter((i,l)=>l===0||i.route.path&&i.route.path.length>0)}function Hf(n){let i=RS(n);return i.map((l,r)=>r===i.length-1?l.pathname:l.pathnameBase)}function no(n,i,l,r=!1){let u;typeof n=="string"?u=Li(n):(u={...n},Gt(!u.pathname||!u.pathname.includes("?"),Hc("?","pathname","search",u)),Gt(!u.pathname||!u.pathname.includes("#"),Hc("#","pathname","hash",u)),Gt(!u.search||!u.search.includes("#"),Hc("#","search","hash",u)));let d=n===""||u.pathname==="",f=d?"/":u.pathname,h;if(f==null)h=l;else{let x=i.length-1;if(!r&&f.startsWith("..")){let S=f.split("/");for(;S[0]==="..";)S.shift(),x-=1;u.pathname=S.join("/")}h=x>=0?i[x]:"/"}let m=wS(u,h),p=f&&f!=="/"&&f.endsWith("/"),v=(d||f===".")&&l.endsWith("/");return!m.pathname.endsWith("/")&&(p||v)&&(m.pathname+="/"),m}var Sg=n=>n.replace(/[\\/]{2,}/g,"/"),tn=n=>Sg(n.join("/")),Xr=n=>n.replace(/\/+$/,""),_S=n=>Xr(n).replace(/^\/*/,"/"),NS=n=>!n||n==="?"?"":n.startsWith("?")?n:"?"+n,jS=n=>!n||n==="#"?"":n.startsWith("#")?n:"#"+n,DS=class{constructor(n,i,l,r=!1){this.status=n,this.statusText=i||"",this.internal=r,l instanceof Error?(this.data=l.toString(),this.error=l):this.data=l}};function MS(n){return n!=null&&typeof n.status=="number"&&typeof n.statusText=="string"&&typeof n.internal=="boolean"&&"data"in n}function OS(n){let i=n.map(l=>l.route.path).filter(Boolean);return tn(i)||"/"}var Tg=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Eg(n,i){let l=n;if(typeof l!="string"||!Bf.test(l))return{absoluteURL:void 0,isExternal:!1,to:l};let r=l,u=!1;if(Tg)try{let d=new URL(window.location.href),f=pg.test(l)?new URL(oS(l,d.protocol)):new URL(l),h=kn(f.pathname,i);f.origin===d.origin&&h!=null?l=h+f.search+f.hash:u=!0}catch{an(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:u,to:l}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Ag=["POST","PUT","PATCH","DELETE"];new Set(Ag);var zS=["GET",...Ag];new Set(zS);var LS=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];function kS(n){try{return LS.includes(new URL(n).protocol)}catch{return!1}}var ki=T.createContext(null);ki.displayName="DataRouter";var ao=T.createContext(null);ao.displayName="DataRouterState";var Cg=T.createContext(!1);function US(){return T.useContext(Cg)}var wg=T.createContext({isTransitioning:!1});wg.displayName="ViewTransition";var VS=T.createContext(new Map);VS.displayName="Fetchers";var BS=T.createContext(null);BS.displayName="Await";var ze=T.createContext(null);ze.displayName="Navigation";var Is=T.createContext(null);Is.displayName="Location";var sn=T.createContext({outlet:null,matches:[],isDataRoute:!1});sn.displayName="Route";var Gf=T.createContext(null);Gf.displayName="RouteError";var Rg="REACT_ROUTER_ERROR",HS="REDIRECT",GS="ROUTE_ERROR_RESPONSE";function YS(n){if(n.startsWith(`${Rg}:${HS}:{`))try{let i=JSON.parse(n.slice(28));if(typeof i=="object"&&i&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.location=="string"&&typeof i.reloadDocument=="boolean"&&typeof i.replace=="boolean")return i}catch{}}function qS(n){if(n.startsWith(`${Rg}:${GS}:{`))try{let i=JSON.parse(n.slice(40));if(typeof i=="object"&&i&&typeof i.status=="number"&&typeof i.statusText=="string")return new DS(i.status,i.statusText,i.data)}catch{}}function PS(n,{relative:i}={}){Gt(Ui(),"useHref() may be used only in the context of a component.");let{basename:l,navigator:r}=T.useContext(ze),{hash:u,pathname:d,search:f}=tl(n,{relative:i}),h=d;return l!=="/"&&(h=d==="/"?l:tn([l,d])),r.createHref({pathname:h,search:f,hash:u})}function Ui(){return T.useContext(Is)!=null}function ln(){return Gt(Ui(),"useLocation() may be used only in the context of a component."),T.useContext(Is).location}var _g="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Ng(n){T.useContext(ze).static||T.useLayoutEffect(n)}function pa(){let{isDataRoute:n}=T.useContext(sn);return n?iT():XS()}function XS(){Gt(Ui(),"useNavigate() may be used only in the context of a component.");let n=T.useContext(ki),{basename:i,navigator:l}=T.useContext(ze),{matches:r}=T.useContext(sn),{pathname:u}=ln(),d=JSON.stringify(Hf(r)),f=T.useRef(!1);return Ng(()=>{f.current=!0}),T.useCallback((m,p={})=>{if(an(f.current,_g),!f.current)return;if(typeof m=="number"){l.go(m);return}let v=no(m,JSON.parse(d),u,p.relative==="path");n==null&&i!=="/"&&(v.pathname=v.pathname==="/"?i:tn([i,v.pathname])),(p.replace?l.replace:l.push)(v,p.state,p)},[i,l,d,u,n])}T.createContext(null);function KS(){let{matches:n}=T.useContext(sn),i=n[n.length-1];return(i==null?void 0:i.params)??{}}function tl(n,{relative:i}={}){let{matches:l}=T.useContext(sn),{pathname:r}=ln(),u=JSON.stringify(Hf(l));return T.useMemo(()=>no(n,JSON.parse(u),r,i==="path"),[n,u,r,i])}function QS(n,i){return jg(n,i)}function jg(n,i,l){var _;Gt(Ui(),"useRoutes() may be used only in the context of a component.");let{navigator:r}=T.useContext(ze),{matches:u}=T.useContext(sn),d=u[u.length-1],f=d?d.params:{},h=d?d.pathname:"/",m=d?d.pathnameBase:"/",p=d&&d.route;{let R=p&&p.path||"";Mg(h,!p||R.endsWith("*")||R.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${h}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let v=ln(),x;if(i){let R=typeof i=="string"?Li(i):i;Gt(m==="/"||((_=R.pathname)==null?void 0:_.startsWith(m)),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${m}" but pathname "${R.pathname}" was given in the \`location\` prop.`),x=R}else x=v;let S=x.pathname||"/",E=S;if(m!=="/"){let R=m.replace(/^\//,"").split("/");E="/"+S.replace(/^\//,"").split("/").slice(R.length).join("/")}let A=l&&l.state.matches.length?l.state.matches.map(R=>Object.assign(R,{route:l.manifest[R.route.id]||R.route})):yg(n,{pathname:E});an(p||A!=null,`No routes matched location "${x.pathname}${x.search}${x.hash}" `),an(A==null||A[A.length-1].route.element!==void 0||A[A.length-1].route.Component!==void 0||A[A.length-1].route.lazy!==void 0,`Matched leaf route at location "${x.pathname}${x.search}${x.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let w=WS(A&&A.map(R=>Object.assign({},R,{params:Object.assign({},f,R.params),pathname:tn([m,r.encodeLocation?r.encodeLocation(R.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:R.pathname]),pathnameBase:R.pathnameBase==="/"?m:tn([m,r.encodeLocation?r.encodeLocation(R.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:R.pathnameBase])})),u,l);return i&&w?T.createElement(Is.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",mask:void 0,...x},navigationType:"POP"}},w):w}function ZS(){let n=aT(),i=MS(n)?`${n.status} ${n.statusText}`:n instanceof Error?n.message:JSON.stringify(n),l=n instanceof Error?n.stack:null,r="rgba(200,200,200, 0.5)",u={padding:"0.5rem",backgroundColor:r},d={padding:"2px 4px",backgroundColor:r},f=null;return console.error("Error handled by React Router default ErrorBoundary:",n),f=T.createElement(T.Fragment,null,T.createElement("p",null,"💿 Hey developer 👋"),T.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",T.createElement("code",{style:d},"ErrorBoundary")," or"," ",T.createElement("code",{style:d},"errorElement")," prop on your route.")),T.createElement(T.Fragment,null,T.createElement("h2",null,"Unexpected Application Error!"),T.createElement("h3",{style:{fontStyle:"italic"}},i),l?T.createElement("pre",{style:u},l):null,f)}var $S=T.createElement(ZS,null),Dg=class extends T.Component{constructor(n){super(n),this.state={location:n.location,revalidation:n.revalidation,error:n.error}}static getDerivedStateFromError(n){return{error:n}}static getDerivedStateFromProps(n,i){return i.location!==n.location||i.revalidation!=="idle"&&n.revalidation==="idle"?{error:n.error,location:n.location,revalidation:n.revalidation}:{error:n.error!==void 0?n.error:i.error,location:i.location,revalidation:n.revalidation||i.revalidation}}componentDidCatch(n,i){this.props.onError?this.props.onError(n,i):console.error("React Router caught the following error during render",n)}render(){let n=this.state.error;if(this.context&&typeof n=="object"&&n&&"digest"in n&&typeof n.digest=="string"){const l=qS(n.digest);l&&(n=l)}let i=n!==void 0?T.createElement(sn.Provider,{value:this.props.routeContext},T.createElement(Gf.Provider,{value:n,children:this.props.component})):this.props.children;return this.context?T.createElement(FS,{error:n},i):i}};Dg.contextType=Cg;var Gc=new WeakMap;function FS({children:n,error:i}){let{basename:l}=T.useContext(ze);if(typeof i=="object"&&i&&"digest"in i&&typeof i.digest=="string"){let r=YS(i.digest);if(r){let u=Gc.get(i);if(u)throw u;let d=Eg(r.location,l),f=d.absoluteURL||d.to;if(kS(f))throw new Error("Invalid redirect location");if(Tg&&!Gc.get(i))if(d.isExternal||r.reloadDocument)window.location.href=f;else{const h=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(d.to,{replace:r.replace}));throw Gc.set(i,h),h}return T.createElement("meta",{httpEquiv:"refresh",content:`0;url=${f}`})}}return n}function JS({routeContext:n,match:i,children:l}){let r=T.useContext(ki);return r&&r.static&&r.staticContext&&(i.route.errorElement||i.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=i.route.id),T.createElement(sn.Provider,{value:n},l)}function WS(n,i=[],l){let r=l==null?void 0:l.state;if(n==null){if(!r)return null;if(r.errors)n=r.matches;else if(i.length===0&&!r.initialized&&r.matches.length>0)n=r.matches;else return null}let u=n,d=r==null?void 0:r.errors;if(d!=null){let v=u.findIndex(x=>x.route.id&&(d==null?void 0:d[x.route.id])!==void 0);Gt(v>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(d).join(",")}`),u=u.slice(0,Math.min(u.length,v+1))}let f=!1,h=-1;if(l&&r){f=r.renderFallback;for(let v=0;v=0?u=u.slice(0,h+1):u=[u[0]];break}}}}let m=l==null?void 0:l.onError,p=r&&m?(v,x)=>{var S,E;m(v,{location:r.location,params:((E=(S=r.matches)==null?void 0:S[0])==null?void 0:E.params)??{},pattern:OS(r.matches),errorInfo:x})}:void 0;return u.reduceRight((v,x,S)=>{let E,A=!1,w=null,_=null;r&&(E=d&&x.route.id?d[x.route.id]:void 0,w=x.route.errorElement||$S,f&&(h<0&&S===0?(Mg("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),A=!0,_=null):h===S&&(A=!0,_=x.route.hydrateFallbackElement||null)));let R=i.concat(u.slice(0,S+1)),O=()=>{let D;return E?D=w:A?D=_:x.route.Component?D=T.createElement(x.route.Component,null):x.route.element?D=x.route.element:D=v,T.createElement(JS,{match:x,routeContext:{outlet:v,matches:R,isDataRoute:r!=null},children:D})};return r&&(x.route.ErrorBoundary||x.route.errorElement||S===0)?T.createElement(Dg,{location:r.location,revalidation:r.revalidation,component:w,error:E,children:O(),routeContext:{outlet:null,matches:R,isDataRoute:!0},onError:p}):O()},null)}function Yf(n){return`${n} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function IS(n){let i=T.useContext(ki);return Gt(i,Yf(n)),i}function tT(n){let i=T.useContext(ao);return Gt(i,Yf(n)),i}function eT(n){let i=T.useContext(sn);return Gt(i,Yf(n)),i}function qf(n){let i=eT(n),l=i.matches[i.matches.length-1];return Gt(l.route.id,`${n} can only be used on routes that contain a unique "id"`),l.route.id}function nT(){return qf("useRouteId")}function aT(){var r;let n=T.useContext(Gf),i=tT("useRouteError"),l=qf("useRouteError");return n!==void 0?n:(r=i.errors)==null?void 0:r[l]}function iT(){let{router:n}=IS("useNavigate"),i=qf("useNavigate"),l=T.useRef(!1);return Ng(()=>{l.current=!0}),T.useCallback(async(u,d={})=>{an(l.current,_g),l.current&&(typeof u=="number"?await n.navigate(u):await n.navigate(u,{fromRouteId:i,...d}))},[n,i])}var Hy={};function Mg(n,i,l){!i&&!Hy[n]&&(Hy[n]=!0,an(!1,l))}T.memo(sT);function sT({routes:n,manifest:i,future:l,state:r,isStatic:u,onError:d}){return jg(n,void 0,{manifest:i,state:r,isStatic:u,onError:d})}function Gy({to:n,replace:i,state:l,relative:r}){Gt(Ui()," may be used only in the context of a component.");let{static:u}=T.useContext(ze);an(!u," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:d}=T.useContext(sn),{pathname:f}=ln(),h=pa(),m=no(n,Hf(d),f,r==="path"),p=JSON.stringify(m);return T.useEffect(()=>{h(JSON.parse(p),{replace:i,state:l,relative:r})},[h,p,r,i,l]),null}function We(n){Gt(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function lT({basename:n="/",children:i=null,location:l,navigationType:r="POP",navigator:u,static:d=!1,useTransitions:f}){Gt(!Ui(),"You cannot render a inside another . You should never have more than one in your app.");let h=n.replace(/^\/*/,"/"),m=T.useMemo(()=>({basename:h,navigator:u,static:d,useTransitions:f,future:{}}),[h,u,d,f]);typeof l=="string"&&(l=Li(l));let{pathname:p="/",search:v="",hash:x="",state:S=null,key:E="default",mask:A}=l,w=T.useMemo(()=>{let _=kn(p,h);return _==null?null:{location:{pathname:_,search:v,hash:x,state:S,key:E,mask:A},navigationType:r}},[h,p,v,x,S,E,r,A]);return an(w!=null,` is not able to match the URL "${p}${v}${x}" because it does not start with the basename, so the won't render anything.`),w==null?null:T.createElement(ze.Provider,{value:m},T.createElement(Is.Provider,{children:i,value:w}))}function Yy({children:n,location:i}){return QS(of(n),i)}function of(n,i=[]){let l=[];return T.Children.forEach(n,(r,u)=>{if(!T.isValidElement(r))return;let d=[...i,u];if(r.type===T.Fragment){l.push.apply(l,of(r.props.children,d));return}Gt(r.type===We,`[${typeof r.type=="string"?r.type:r.type.name}] is not a component. All component children of must be a or `),Gt(!r.props.index||!r.props.children,"An index route cannot have child routes.");let f={id:r.props.id||d.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,middleware:r.props.middleware,loader:r.props.loader,action:r.props.action,hydrateFallbackElement:r.props.hydrateFallbackElement,HydrateFallback:r.props.HydrateFallback,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.hasErrorBoundary===!0||r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(f.children=of(r.props.children,d)),l.push(f)}),l}var zr="get",Lr="application/x-www-form-urlencoded";function io(n){return typeof HTMLElement<"u"&&n instanceof HTMLElement}function rT(n){return io(n)&&n.tagName.toLowerCase()==="button"}function oT(n){return io(n)&&n.tagName.toLowerCase()==="form"}function uT(n){return io(n)&&n.tagName.toLowerCase()==="input"}function cT(n){return!!(n.metaKey||n.altKey||n.ctrlKey||n.shiftKey)}function fT(n,i){return n.button===0&&(!i||i==="_self")&&!cT(n)}var Cr=null;function dT(){if(Cr===null)try{new FormData(document.createElement("form"),0),Cr=!1}catch{Cr=!0}return Cr}var hT=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Yc(n){return n!=null&&!hT.has(n)?(an(!1,`"${n}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Lr}"`),null):n}function mT(n,i){let l,r,u,d,f;if(oT(n)){let h=n.getAttribute("action");r=h?kn(h,i):null,l=n.getAttribute("method")||zr,u=Yc(n.getAttribute("enctype"))||Lr,d=new FormData(n)}else if(rT(n)||uT(n)&&(n.type==="submit"||n.type==="image")){let h=n.form;if(h==null)throw new Error('Cannot submit a