wyxrouter 0.4.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +222 -0
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/README.zh-CN.md +1311 -0
- package/assets/pixel-router-2d.png +0 -0
- package/cli/LICENSE +42 -0
- package/cli/README.md +125 -0
- package/cli/app/package.json +58 -0
- package/cli/cli.js +806 -0
- package/cli/package.json +48 -0
- package/i18n/README.ja-JP.md +1209 -0
- package/i18n/README.ru.md +1311 -0
- package/i18n/README.vi.md +1310 -0
- package/i18n/README.zh-CN.md +1306 -0
- package/images/9router.png +0 -0
- package/jsconfig.json +12 -0
- package/next.config.mjs +71 -0
- package/open-sse/config/appConstants.js +203 -0
- package/open-sse/config/codexInstructions.js +119 -0
- package/open-sse/config/constants.js +4 -0
- package/open-sse/config/defaultThinkingSignature.js +12 -0
- package/open-sse/config/errorConfig.js +85 -0
- package/open-sse/config/googleTtsLanguages.js +62 -0
- package/open-sse/config/kiroConstants.js +322 -0
- package/open-sse/config/models.js +13 -0
- package/open-sse/config/ollamaModels.js +19 -0
- package/open-sse/config/providerModels.js +944 -0
- package/open-sse/config/providers.js +458 -0
- package/open-sse/config/runtimeConfig.js +72 -0
- package/open-sse/config/ttsModels.js +129 -0
- package/open-sse/executors/antigravity.js +504 -0
- package/open-sse/executors/azure.js +57 -0
- package/open-sse/executors/base.js +185 -0
- package/open-sse/executors/codex.js +469 -0
- package/open-sse/executors/commandcode.js +88 -0
- package/open-sse/executors/cursor.js +795 -0
- package/open-sse/executors/default.js +497 -0
- package/open-sse/executors/gemini-cli.js +89 -0
- package/open-sse/executors/github.js +379 -0
- package/open-sse/executors/grok-web.js +345 -0
- package/open-sse/executors/iflow.js +108 -0
- package/open-sse/executors/index.js +75 -0
- package/open-sse/executors/kiro.js +508 -0
- package/open-sse/executors/ollama-local.js +14 -0
- package/open-sse/executors/opencode-go.js +41 -0
- package/open-sse/executors/opencode.js +32 -0
- package/open-sse/executors/perplexity-web.js +507 -0
- package/open-sse/executors/qoder.js +450 -0
- package/open-sse/executors/qwen.js +129 -0
- package/open-sse/executors/vertex.js +131 -0
- package/open-sse/executors/xiaomi-tokenplan.js +19 -0
- package/open-sse/handlers/chatCore/nonStreamingHandler.js +230 -0
- package/open-sse/handlers/chatCore/requestDetail.js +102 -0
- package/open-sse/handlers/chatCore/sseToJsonHandler.js +231 -0
- package/open-sse/handlers/chatCore/streamingHandler.js +103 -0
- package/open-sse/handlers/chatCore.js +287 -0
- package/open-sse/handlers/embeddingProviders/_base.js +4 -0
- package/open-sse/handlers/embeddingProviders/gemini.js +54 -0
- package/open-sse/handlers/embeddingProviders/index.js +23 -0
- package/open-sse/handlers/embeddingProviders/openai.js +39 -0
- package/open-sse/handlers/embeddingProviders/openaiCompatNode.js +13 -0
- package/open-sse/handlers/embeddingsCore.js +126 -0
- package/open-sse/handlers/fetch/index.js +237 -0
- package/open-sse/handlers/imageGenerationCore.js +189 -0
- package/open-sse/handlers/imageProviders/_base.js +31 -0
- package/open-sse/handlers/imageProviders/blackForestLabs.js +43 -0
- package/open-sse/handlers/imageProviders/cloudflareAi.js +178 -0
- package/open-sse/handlers/imageProviders/codex.js +198 -0
- package/open-sse/handlers/imageProviders/comfyui.js +8 -0
- package/open-sse/handlers/imageProviders/falAi.js +41 -0
- package/open-sse/handlers/imageProviders/gemini.js +25 -0
- package/open-sse/handlers/imageProviders/huggingface.js +22 -0
- package/open-sse/handlers/imageProviders/index.js +40 -0
- package/open-sse/handlers/imageProviders/nanobanana.js +58 -0
- package/open-sse/handlers/imageProviders/openai.js +40 -0
- package/open-sse/handlers/imageProviders/runwayml.js +47 -0
- package/open-sse/handlers/imageProviders/sdwebui.js +17 -0
- package/open-sse/handlers/imageProviders/stabilityAi.js +34 -0
- package/open-sse/handlers/responsesHandler.js +103 -0
- package/open-sse/handlers/search/callers.js +371 -0
- package/open-sse/handlers/search/chatSearch.js +409 -0
- package/open-sse/handlers/search/index.js +201 -0
- package/open-sse/handlers/search/normalizers.js +223 -0
- package/open-sse/handlers/sttCore.js +194 -0
- package/open-sse/handlers/ttsCore.js +74 -0
- package/open-sse/handlers/ttsProviders/_base.js +39 -0
- package/open-sse/handlers/ttsProviders/edgeTts.js +89 -0
- package/open-sse/handlers/ttsProviders/elevenlabs.js +48 -0
- package/open-sse/handlers/ttsProviders/gemini.js +117 -0
- package/open-sse/handlers/ttsProviders/genericFormats.js +169 -0
- package/open-sse/handlers/ttsProviders/googleTts.js +54 -0
- package/open-sse/handlers/ttsProviders/index.js +50 -0
- package/open-sse/handlers/ttsProviders/localDevice.js +87 -0
- package/open-sse/handlers/ttsProviders/minimax.js +59 -0
- package/open-sse/handlers/ttsProviders/openai.js +30 -0
- package/open-sse/handlers/ttsProviders/openrouter.js +70 -0
- package/open-sse/index.js +82 -0
- package/open-sse/rtk/applyFilter.js +15 -0
- package/open-sse/rtk/autodetect.js +111 -0
- package/open-sse/rtk/caveman.js +100 -0
- package/open-sse/rtk/cavemanPrompts.js +78 -0
- package/open-sse/rtk/constants.js +55 -0
- package/open-sse/rtk/filters/buildOutput.js +127 -0
- package/open-sse/rtk/filters/dedupLog.js +44 -0
- package/open-sse/rtk/filters/find.js +49 -0
- package/open-sse/rtk/filters/gitDiff.js +92 -0
- package/open-sse/rtk/filters/gitStatus.js +117 -0
- package/open-sse/rtk/filters/grep.js +48 -0
- package/open-sse/rtk/filters/ls.js +79 -0
- package/open-sse/rtk/filters/readNumbered.js +27 -0
- package/open-sse/rtk/filters/searchList.js +52 -0
- package/open-sse/rtk/filters/smartTruncate.js +15 -0
- package/open-sse/rtk/filters/tree.js +32 -0
- package/open-sse/rtk/index.js +155 -0
- package/open-sse/rtk/registry.js +38 -0
- package/open-sse/services/accountFallback.js +238 -0
- package/open-sse/services/combo.js +198 -0
- package/open-sse/services/compact.js +71 -0
- package/open-sse/services/kiroModels.js +332 -0
- package/open-sse/services/model.js +261 -0
- package/open-sse/services/oauthCredentialManager.js +151 -0
- package/open-sse/services/projectId.js +306 -0
- package/open-sse/services/provider.js +356 -0
- package/open-sse/services/qoderModels.js +214 -0
- package/open-sse/services/tokenRefresh.js +939 -0
- package/open-sse/services/usage.js +1496 -0
- package/open-sse/transformer/responsesTransformer.js +439 -0
- package/open-sse/transformer/streamToJsonConverter.js +103 -0
- package/open-sse/translator/formats.js +36 -0
- package/open-sse/translator/helpers/claudeHelper.js +216 -0
- package/open-sse/translator/helpers/geminiHelper.js +372 -0
- package/open-sse/translator/helpers/imageHelper.js +34 -0
- package/open-sse/translator/helpers/maxTokensHelper.js +27 -0
- package/open-sse/translator/helpers/openaiHelper.js +130 -0
- package/open-sse/translator/helpers/responsesApiHelper.js +139 -0
- package/open-sse/translator/helpers/toolCallHelper.js +148 -0
- package/open-sse/translator/index.js +251 -0
- package/open-sse/translator/request/antigravity-to-openai.js +229 -0
- package/open-sse/translator/request/claude-to-openai.js +232 -0
- package/open-sse/translator/request/gemini-to-openai.js +147 -0
- package/open-sse/translator/request/openai-responses.js +318 -0
- package/open-sse/translator/request/openai-to-claude.js +401 -0
- package/open-sse/translator/request/openai-to-commandcode.js +170 -0
- package/open-sse/translator/request/openai-to-cursor.js +183 -0
- package/open-sse/translator/request/openai-to-gemini.js +470 -0
- package/open-sse/translator/request/openai-to-kiro.js +629 -0
- package/open-sse/translator/request/openai-to-kiro.old.js +278 -0
- package/open-sse/translator/request/openai-to-ollama.js +192 -0
- package/open-sse/translator/request/openai-to-vertex.js +42 -0
- package/open-sse/translator/response/claude-to-openai.js +206 -0
- package/open-sse/translator/response/commandcode-to-openai.js +197 -0
- package/open-sse/translator/response/cursor-to-openai.js +30 -0
- package/open-sse/translator/response/gemini-to-openai.js +245 -0
- package/open-sse/translator/response/kiro-to-openai.js +195 -0
- package/open-sse/translator/response/ollama-to-openai.js +152 -0
- package/open-sse/translator/response/openai-responses.js +590 -0
- package/open-sse/translator/response/openai-to-antigravity.js +122 -0
- package/open-sse/translator/response/openai-to-claude.js +266 -0
- package/open-sse/utils/bypassHandler.js +298 -0
- package/open-sse/utils/claudeCloaking.js +155 -0
- package/open-sse/utils/claudeHeaderCache.js +70 -0
- package/open-sse/utils/clientDetector.js +63 -0
- package/open-sse/utils/cursorChecksum.js +149 -0
- package/open-sse/utils/cursorProtobuf.js +904 -0
- package/open-sse/utils/debugLog.js +14 -0
- package/open-sse/utils/error.js +147 -0
- package/open-sse/utils/ollamaTransform.js +85 -0
- package/open-sse/utils/proxyFetch.js +368 -0
- package/open-sse/utils/reasoningContentInjector.js +79 -0
- package/open-sse/utils/requestLogger.js +260 -0
- package/open-sse/utils/responsesStreamHelpers.js +49 -0
- package/open-sse/utils/sessionManager.js +82 -0
- package/open-sse/utils/stream.js +462 -0
- package/open-sse/utils/streamHandler.js +250 -0
- package/open-sse/utils/streamHelpers.js +122 -0
- package/open-sse/utils/toolDeduper.js +49 -0
- package/open-sse/utils/usageTracking.js +347 -0
- package/package.json +100 -0
- package/postcss.config.mjs +12 -0
- package/public/favicon.svg +11 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/i18n/literals/ar.json +195 -0
- package/public/i18n/literals/bn.json +195 -0
- package/public/i18n/literals/cs.json +195 -0
- package/public/i18n/literals/da.json +195 -0
- package/public/i18n/literals/de.json +195 -0
- package/public/i18n/literals/el.json +195 -0
- package/public/i18n/literals/es.json +195 -0
- package/public/i18n/literals/fi.json +195 -0
- package/public/i18n/literals/fr.json +195 -0
- package/public/i18n/literals/he.json +195 -0
- package/public/i18n/literals/hi.json +195 -0
- package/public/i18n/literals/hu.json +195 -0
- package/public/i18n/literals/id.json +195 -0
- package/public/i18n/literals/it.json +195 -0
- package/public/i18n/literals/ja.json +195 -0
- package/public/i18n/literals/ko.json +195 -0
- package/public/i18n/literals/nl.json +195 -0
- package/public/i18n/literals/no.json +195 -0
- package/public/i18n/literals/pl.json +195 -0
- package/public/i18n/literals/pt-BR.json +195 -0
- package/public/i18n/literals/pt-PT.json +195 -0
- package/public/i18n/literals/ro.json +195 -0
- package/public/i18n/literals/ru.json +195 -0
- package/public/i18n/literals/sv.json +195 -0
- package/public/i18n/literals/th.json +195 -0
- package/public/i18n/literals/tl.json +195 -0
- package/public/i18n/literals/tr.json +195 -0
- package/public/i18n/literals/uk.json +195 -0
- package/public/i18n/literals/ur.json +195 -0
- package/public/i18n/literals/vi.json +195 -0
- package/public/i18n/literals/zh-CN.json +772 -0
- package/public/i18n/literals/zh-TW.json +195 -0
- package/public/icons/discord.svg +4 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/next.svg +1 -0
- package/public/providers/alicode-intl.png +0 -0
- package/public/providers/alicode.png +0 -0
- package/public/providers/amp.png +0 -0
- package/public/providers/anthropic-m.png +0 -0
- package/public/providers/anthropic.png +0 -0
- package/public/providers/antigravity.png +0 -0
- package/public/providers/assemblyai.png +0 -0
- package/public/providers/aws-polly.png +0 -0
- package/public/providers/azure.png +0 -0
- package/public/providers/black-forest-labs.png +0 -0
- package/public/providers/blackbox.png +0 -0
- package/public/providers/brave-search.png +0 -0
- package/public/providers/byteplus.png +0 -0
- package/public/providers/cartesia.png +0 -0
- package/public/providers/cerebras.png +0 -0
- package/public/providers/chutes.png +0 -0
- package/public/providers/claude.png +0 -0
- package/public/providers/cline.png +0 -0
- package/public/providers/cloudflare-ai.png +0 -0
- package/public/providers/codebuddy.svg +37 -0
- package/public/providers/codex.png +0 -0
- package/public/providers/cohere.png +0 -0
- package/public/providers/comfyui.png +0 -0
- package/public/providers/commandcode.png +0 -0
- package/public/providers/continue.png +0 -0
- package/public/providers/copilot.png +0 -0
- package/public/providers/coqui.png +0 -0
- package/public/providers/cursor.png +0 -0
- package/public/providers/deepgram.png +0 -0
- package/public/providers/deepseek-tui.png +0 -0
- package/public/providers/deepseek.png +0 -0
- package/public/providers/droid.png +0 -0
- package/public/providers/edge-tts.png +0 -0
- package/public/providers/elevenlabs.png +0 -0
- package/public/providers/exa.png +0 -0
- package/public/providers/fal-ai.png +0 -0
- package/public/providers/firecrawl.png +0 -0
- package/public/providers/fireworks.png +0 -0
- package/public/providers/gemini-cli.png +0 -0
- package/public/providers/gemini.png +0 -0
- package/public/providers/github.png +0 -0
- package/public/providers/glm-cn.png +0 -0
- package/public/providers/glm.png +0 -0
- package/public/providers/google-pse.png +0 -0
- package/public/providers/google-tts.png +0 -0
- package/public/providers/grok-web.png +0 -0
- package/public/providers/groq.png +0 -0
- package/public/providers/hermes.png +0 -0
- package/public/providers/huggingface.png +0 -0
- package/public/providers/hyperbolic.png +0 -0
- package/public/providers/iflow.png +0 -0
- package/public/providers/inworld.png +0 -0
- package/public/providers/jcode.png +0 -0
- package/public/providers/jina-ai.png +0 -0
- package/public/providers/jina-reader.png +0 -0
- package/public/providers/kilocode.png +0 -0
- package/public/providers/kimi-coding.png +0 -0
- package/public/providers/kimi.png +0 -0
- package/public/providers/kiro.png +0 -0
- package/public/providers/linkup.png +0 -0
- package/public/providers/local-device.png +0 -0
- package/public/providers/minimax-cn.png +0 -0
- package/public/providers/minimax.png +0 -0
- package/public/providers/mistral.png +0 -0
- package/public/providers/nanobanana.png +0 -0
- package/public/providers/nebius.png +0 -0
- package/public/providers/nvidia.png +0 -0
- package/public/providers/oai-cc.png +0 -0
- package/public/providers/oai-r.png +0 -0
- package/public/providers/ollama-local.png +0 -0
- package/public/providers/ollama.png +0 -0
- package/public/providers/openai.png +0 -0
- package/public/providers/openclaw.png +0 -0
- package/public/providers/opencode-go.png +0 -0
- package/public/providers/opencode.png +0 -0
- package/public/providers/openrouter.png +0 -0
- package/public/providers/perplexity-web.png +0 -0
- package/public/providers/perplexity.png +0 -0
- package/public/providers/playht.png +0 -0
- package/public/providers/qoder.png +0 -0
- package/public/providers/qwen.png +0 -0
- package/public/providers/recraft.png +0 -0
- package/public/providers/roo.png +0 -0
- package/public/providers/runwayml.png +0 -0
- package/public/providers/sdwebui.png +0 -0
- package/public/providers/searchapi.png +0 -0
- package/public/providers/searxng.png +0 -0
- package/public/providers/serper.png +0 -0
- package/public/providers/siliconflow.png +0 -0
- package/public/providers/stability-ai.png +0 -0
- package/public/providers/tavily.png +0 -0
- package/public/providers/together.png +0 -0
- package/public/providers/topaz.png +0 -0
- package/public/providers/tortoise.png +0 -0
- package/public/providers/vertex-partner.png +0 -0
- package/public/providers/vertex.png +0 -0
- package/public/providers/volcengine-ark.png +0 -0
- package/public/providers/voyage-ai.png +0 -0
- package/public/providers/xai.png +0 -0
- package/public/providers/xiaomi-mimo.png +0 -0
- package/public/providers/xiaomi-tokenplan.png +0 -0
- package/public/providers/youcom.png +0 -0
- package/public/sw.js +22 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/compact-request-details.mjs +71 -0
- package/scripts/import-codex-gptjson.mjs +342 -0
- package/scripts/start-standalone.mjs +25 -0
- package/scripts/translate-readme.js +201 -0
- package/src/app/(dashboard)/dashboard/automation/page.js +294 -0
- package/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js +967 -0
- package/src/app/(dashboard)/dashboard/basic-chat/page.js +5 -0
- package/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js +66 -0
- package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js +173 -0
- package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js +11 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +481 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ApiKeySelect.js +66 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/BaseUrlSelect.js +174 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js +390 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js +301 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +458 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js +323 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js +640 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js +338 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js +271 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +410 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/EndpointPresetControl.js +128 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js +317 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js +380 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js +275 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmLinkCard.js +40 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js +329 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js +318 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js +388 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js +500 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js +39 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/cliEndpointMatch.js +13 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/index.js +19 -0
- package/src/app/(dashboard)/dashboard/cli-tools/page.js +7 -0
- package/src/app/(dashboard)/dashboard/combos/page.js +612 -0
- package/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js +91 -0
- package/src/app/(dashboard)/dashboard/console-log/page.js +8 -0
- package/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js +1555 -0
- package/src/app/(dashboard)/dashboard/endpoint/page.js +7 -0
- package/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js +1903 -0
- package/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js +289 -0
- package/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js +410 -0
- package/src/app/(dashboard)/dashboard/media-providers/web/page.js +209 -0
- package/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +117 -0
- package/src/app/(dashboard)/dashboard/mitm/page.js +5 -0
- package/src/app/(dashboard)/dashboard/page.js +7 -0
- package/src/app/(dashboard)/dashboard/profile/page.js +1140 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +389 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js +125 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js +250 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js +299 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/CooldownTimer.js +42 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js +161 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js +95 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/PassthroughModelsSection.js +183 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/page.js +2053 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/page.new.js +1724 -0
- package/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js +497 -0
- package/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js +185 -0
- package/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js +294 -0
- package/src/app/(dashboard)/dashboard/providers/new/page.js +220 -0
- package/src/app/(dashboard)/dashboard/providers/page.js +1365 -0
- package/src/app/(dashboard)/dashboard/proxy-pools/page.js +1092 -0
- package/src/app/(dashboard)/dashboard/quota/page.js +11 -0
- package/src/app/(dashboard)/dashboard/skills/page.js +112 -0
- package/src/app/(dashboard)/dashboard/translator/page.js +303 -0
- package/src/app/(dashboard)/dashboard/usage/components/OverviewCards.js +35 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/ProviderLimitCard.js +185 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js +127 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js +259 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +1394 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +244 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderTopology.js +327 -0
- package/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js +433 -0
- package/src/app/(dashboard)/dashboard/usage/components/UsageChart.js +141 -0
- package/src/app/(dashboard)/dashboard/usage/components/UsageTable.js +247 -0
- package/src/app/(dashboard)/dashboard/usage/page.js +75 -0
- package/src/app/(dashboard)/layout.js +6 -0
- package/src/app/api/auth/login/route.js +76 -0
- package/src/app/api/auth/logout/route.js +12 -0
- package/src/app/api/auth/oidc/callback/route.js +87 -0
- package/src/app/api/auth/oidc/start/route.js +52 -0
- package/src/app/api/auth/oidc/test/route.js +84 -0
- package/src/app/api/auth/status/route.js +45 -0
- package/src/app/api/cli-tools/all-statuses/route.js +46 -0
- package/src/app/api/cli-tools/antigravity-mitm/alias/route.js +53 -0
- package/src/app/api/cli-tools/antigravity-mitm/route.js +202 -0
- package/src/app/api/cli-tools/claude-settings/route.js +203 -0
- package/src/app/api/cli-tools/cline-settings/route.js +133 -0
- package/src/app/api/cli-tools/codex-gateway/accounts/route.js +16 -0
- package/src/app/api/cli-tools/codex-settings/route.js +239 -0
- package/src/app/api/cli-tools/copilot-settings/route.js +148 -0
- package/src/app/api/cli-tools/cowork-mcp-registry/route.js +77 -0
- package/src/app/api/cli-tools/cowork-mcp-tools/route.js +95 -0
- package/src/app/api/cli-tools/cowork-settings/route.js +412 -0
- package/src/app/api/cli-tools/deepseek-tui-settings/route.js +164 -0
- package/src/app/api/cli-tools/droid-settings/route.js +213 -0
- package/src/app/api/cli-tools/hermes-settings/route.js +175 -0
- package/src/app/api/cli-tools/jcode-settings/route.js +216 -0
- package/src/app/api/cli-tools/kilo-settings/route.js +131 -0
- package/src/app/api/cli-tools/openclaw-settings/route.js +292 -0
- package/src/app/api/cli-tools/opencode-settings/route.js +259 -0
- package/src/app/api/combos/[id]/route.js +81 -0
- package/src/app/api/combos/route.js +48 -0
- package/src/app/api/health/route.js +15 -0
- package/src/app/api/init/route.js +4 -0
- package/src/app/api/keys/[id]/route.js +58 -0
- package/src/app/api/keys/route.js +42 -0
- package/src/app/api/locale/route.js +30 -0
- package/src/app/api/mcp/[plugin]/message/route.js +21 -0
- package/src/app/api/mcp/[plugin]/sse/route.js +37 -0
- package/src/app/api/media-providers/tts/deepgram/voices/route.js +65 -0
- package/src/app/api/media-providers/tts/elevenlabs/voices/route.js +71 -0
- package/src/app/api/media-providers/tts/inworld/voices/route.js +61 -0
- package/src/app/api/media-providers/tts/minimax/voices/route.js +113 -0
- package/src/app/api/media-providers/tts/voices/route.js +99 -0
- package/src/app/api/models/alias/route.js +53 -0
- package/src/app/api/models/availability/route.js +103 -0
- package/src/app/api/models/custom/route.js +48 -0
- package/src/app/api/models/disabled/route.js +50 -0
- package/src/app/api/models/route.js +64 -0
- package/src/app/api/models/test/ping.js +191 -0
- package/src/app/api/models/test/route.js +14 -0
- package/src/app/api/oauth/[provider]/[action]/route.js +343 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/cancel/route.js +19 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/manual/[workerId]/route.js +30 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/route.js +23 -0
- package/src/app/api/oauth/codebuddy/bulk-import/latest/route.js +25 -0
- package/src/app/api/oauth/codebuddy/bulk-import/route.js +49 -0
- package/src/app/api/oauth/codebuddy/quota-cookie/route.js +133 -0
- package/src/app/api/oauth/codex/import-token/route.js +96 -0
- package/src/app/api/oauth/cursor/auto-import/route.js +258 -0
- package/src/app/api/oauth/cursor/import/route.js +100 -0
- package/src/app/api/oauth/gitlab/pat/route.js +62 -0
- package/src/app/api/oauth/iflow/cookie/route.js +137 -0
- package/src/app/api/oauth/kiro/auto-import/route.js +85 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/cancel/route.js +18 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/manual/[workerId]/route.js +29 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/route.js +22 -0
- package/src/app/api/oauth/kiro/bulk-import/latest/route.js +25 -0
- package/src/app/api/oauth/kiro/bulk-import/route.js +49 -0
- package/src/app/api/oauth/kiro/import/route.js +110 -0
- package/src/app/api/oauth/kiro/social-authorize/route.js +27 -0
- package/src/app/api/oauth/kiro/social-exchange/route.js +41 -0
- package/src/app/api/pricing/route.js +134 -0
- package/src/app/api/provider-nodes/[id]/route.js +101 -0
- package/src/app/api/provider-nodes/route.js +104 -0
- package/src/app/api/provider-nodes/validate/route.js +201 -0
- package/src/app/api/providers/[id]/models/route.js +526 -0
- package/src/app/api/providers/[id]/route.js +189 -0
- package/src/app/api/providers/[id]/test/route.js +23 -0
- package/src/app/api/providers/[id]/test/testUtils.js +714 -0
- package/src/app/api/providers/[id]/test-models/route.js +66 -0
- package/src/app/api/providers/client/route.js +126 -0
- package/src/app/api/providers/kilo/free-models/route.js +55 -0
- package/src/app/api/providers/route.js +206 -0
- package/src/app/api/providers/suggested-models/filters.js +20 -0
- package/src/app/api/providers/suggested-models/route.js +32 -0
- package/src/app/api/providers/test-batch/route.js +131 -0
- package/src/app/api/providers/validate/route.js +637 -0
- package/src/app/api/proxy-pools/[id]/route.js +123 -0
- package/src/app/api/proxy-pools/[id]/test/route.js +70 -0
- package/src/app/api/proxy-pools/cloudflare-deploy/route.js +145 -0
- package/src/app/api/proxy-pools/deno-deploy/route.js +175 -0
- package/src/app/api/proxy-pools/route.js +93 -0
- package/src/app/api/proxy-pools/vercel-deploy/route.js +142 -0
- package/src/app/api/settings/database/route.js +36 -0
- package/src/app/api/settings/proxy-test/route.js +23 -0
- package/src/app/api/settings/require-login/route.js +15 -0
- package/src/app/api/settings/route.js +100 -0
- package/src/app/api/shutdown/route.js +24 -0
- package/src/app/api/tags/route.js +18 -0
- package/src/app/api/translator/console-logs/route.js +24 -0
- package/src/app/api/translator/console-logs/stream/route.js +79 -0
- package/src/app/api/translator/load/route.js +45 -0
- package/src/app/api/translator/save/route.js +44 -0
- package/src/app/api/translator/send/route.js +94 -0
- package/src/app/api/translator/translate/route.js +90 -0
- package/src/app/api/tunnel/disable/route.js +12 -0
- package/src/app/api/tunnel/enable/route.js +16 -0
- package/src/app/api/tunnel/status/route.js +13 -0
- package/src/app/api/tunnel/tailscale-check/route.js +50 -0
- package/src/app/api/tunnel/tailscale-disable/route.js +12 -0
- package/src/app/api/tunnel/tailscale-enable/route.js +12 -0
- package/src/app/api/tunnel/tailscale-install/route.js +72 -0
- package/src/app/api/usage/[connectionId]/route.js +188 -0
- package/src/app/api/usage/chart/route.js +21 -0
- package/src/app/api/usage/history/route.js +12 -0
- package/src/app/api/usage/logs/route.js +12 -0
- package/src/app/api/usage/providers/route.js +42 -0
- package/src/app/api/usage/request-details/route.js +57 -0
- package/src/app/api/usage/request-logs/route.js +13 -0
- package/src/app/api/usage/stats/route.js +23 -0
- package/src/app/api/usage/stream/route.js +79 -0
- package/src/app/api/v1/api/chat/route.js +37 -0
- package/src/app/api/v1/audio/speech/route.js +16 -0
- package/src/app/api/v1/audio/transcriptions/route.js +19 -0
- package/src/app/api/v1/audio/voices/route.js +68 -0
- package/src/app/api/v1/chat/completions/route.js +35 -0
- package/src/app/api/v1/embeddings/route.js +21 -0
- package/src/app/api/v1/images/generations/route.js +16 -0
- package/src/app/api/v1/messages/count_tokens/route.js +52 -0
- package/src/app/api/v1/messages/route.js +36 -0
- package/src/app/api/v1/models/[kind]/route.js +55 -0
- package/src/app/api/v1/models/info/route.js +110 -0
- package/src/app/api/v1/models/route.js +451 -0
- package/src/app/api/v1/responses/compact/route.js +37 -0
- package/src/app/api/v1/responses/route.js +30 -0
- package/src/app/api/v1/route.js +1 -0
- package/src/app/api/v1/search/route.js +21 -0
- package/src/app/api/v1/web/fetch/route.js +21 -0
- package/src/app/api/v1beta/models/[...path]/route.js +328 -0
- package/src/app/api/v1beta/models/route.js +44 -0
- package/src/app/api/version/route.js +45 -0
- package/src/app/api/version/shutdown/route.js +15 -0
- package/src/app/api/version/update/route.js +21 -0
- package/src/app/callback/page.js +148 -0
- package/src/app/dashboard/settings/pricing/page.js +173 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +496 -0
- package/src/app/landing/components/AnimatedBackground.js +57 -0
- package/src/app/landing/components/Features.js +133 -0
- package/src/app/landing/components/FlowAnimation.js +175 -0
- package/src/app/landing/components/Footer.js +61 -0
- package/src/app/landing/components/GetStarted.js +97 -0
- package/src/app/landing/components/HeroSection.js +47 -0
- package/src/app/landing/components/HowItWorks.js +66 -0
- package/src/app/landing/components/Navigation.js +72 -0
- package/src/app/landing/page.js +106 -0
- package/src/app/layout.js +49 -0
- package/src/app/login/page.js +197 -0
- package/src/app/manifest.js +30 -0
- package/src/app/page.js +5 -0
- package/src/dashboardGuard.js +242 -0
- package/src/i18n/RuntimeI18nProvider.js +27 -0
- package/src/i18n/config.js +146 -0
- package/src/i18n/runtime.js +162 -0
- package/src/lib/appUpdater.js +200 -0
- package/src/lib/auth/dashboardSession.js +68 -0
- package/src/lib/auth/loginLimiter.js +52 -0
- package/src/lib/auth/oidc.js +234 -0
- package/src/lib/consoleLogBuffer.js +79 -0
- package/src/lib/dataDir.js +29 -0
- package/src/lib/db/adapters/betterSqliteAdapter.js +55 -0
- package/src/lib/db/adapters/bunSqliteAdapter.js +63 -0
- package/src/lib/db/adapters/nodeSqliteAdapter.js +84 -0
- package/src/lib/db/adapters/sqljsAdapter.js +115 -0
- package/src/lib/db/backup.js +35 -0
- package/src/lib/db/driver.js +85 -0
- package/src/lib/db/helpers/jsonCol.js +9 -0
- package/src/lib/db/helpers/kvStore.js +39 -0
- package/src/lib/db/helpers/metaStore.js +22 -0
- package/src/lib/db/index.js +171 -0
- package/src/lib/db/migrate.js +286 -0
- package/src/lib/db/migrations/001-initial.js +14 -0
- package/src/lib/db/migrations/index.js +10 -0
- package/src/lib/db/paths.js +18 -0
- package/src/lib/db/repos/aliasRepo.js +62 -0
- package/src/lib/db/repos/apiKeysRepo.js +75 -0
- package/src/lib/db/repos/combosRepo.js +73 -0
- package/src/lib/db/repos/connectionsRepo.js +226 -0
- package/src/lib/db/repos/disabledModelsRepo.js +56 -0
- package/src/lib/db/repos/nodesRepo.js +95 -0
- package/src/lib/db/repos/pricingRepo.js +108 -0
- package/src/lib/db/repos/proxyPoolsRepo.js +103 -0
- package/src/lib/db/repos/requestDetailsRepo.js +259 -0
- package/src/lib/db/repos/settingsRepo.js +104 -0
- package/src/lib/db/repos/usageRepo.js +731 -0
- package/src/lib/db/schema.js +157 -0
- package/src/lib/db/version.js +21 -0
- package/src/lib/disabledModelsDb.js +4 -0
- package/src/lib/localDb.js +21 -0
- package/src/lib/mcp/stdioSseBridge.js +198 -0
- package/src/lib/mitmAliasCache.js +46 -0
- package/src/lib/network/connectionProxy.js +160 -0
- package/src/lib/network/initOutboundProxy.js +25 -0
- package/src/lib/network/outboundProxy.js +68 -0
- package/src/lib/network/proxyTest.js +91 -0
- package/src/lib/oauth/constants/oauth.js +284 -0
- package/src/lib/oauth/constants/xai.js +61 -0
- package/src/lib/oauth/providers.js +1506 -0
- package/src/lib/oauth/services/antigravity.js +321 -0
- package/src/lib/oauth/services/claude.js +136 -0
- package/src/lib/oauth/services/codebuddyBulkImportManager.js +454 -0
- package/src/lib/oauth/services/codex.js +144 -0
- package/src/lib/oauth/services/cursor.js +179 -0
- package/src/lib/oauth/services/gemini.js +240 -0
- package/src/lib/oauth/services/github.js +225 -0
- package/src/lib/oauth/services/iflow.js +202 -0
- package/src/lib/oauth/services/index.js +17 -0
- package/src/lib/oauth/services/kiro.js +334 -0
- package/src/lib/oauth/services/kiroBulkImportManager.js +778 -0
- package/src/lib/oauth/services/kiroConnections.js +93 -0
- package/src/lib/oauth/services/kiroGoogleAutomation.js +1136 -0
- package/src/lib/oauth/services/oauth.js +157 -0
- package/src/lib/oauth/services/openai.js +123 -0
- package/src/lib/oauth/services/qoder.js +216 -0
- package/src/lib/oauth/services/qwen.js +170 -0
- package/src/lib/oauth/services/xai.js +238 -0
- package/src/lib/oauth/utils/banner.js +63 -0
- package/src/lib/oauth/utils/pkce.js +39 -0
- package/src/lib/oauth/utils/server.js +415 -0
- package/src/lib/oauth/utils/ui.js +48 -0
- package/src/lib/providerNormalization.js +45 -0
- package/src/lib/qoder/constants.js +64 -0
- package/src/lib/qoder/cosy.js +175 -0
- package/src/lib/qoder/encoding.js +55 -0
- package/src/lib/requestDetailsDb.js +4 -0
- package/src/lib/tunnel/cloudflare/cloudflared.js +449 -0
- package/src/lib/tunnel/cloudflare/config.js +9 -0
- package/src/lib/tunnel/cloudflare/healthCheck.js +29 -0
- package/src/lib/tunnel/cloudflare/manager.js +151 -0
- package/src/lib/tunnel/cloudflare/pid.js +23 -0
- package/src/lib/tunnel/index.js +48 -0
- package/src/lib/tunnel/shared/dnsResolver.js +17 -0
- package/src/lib/tunnel/shared/internetCheck.js +26 -0
- package/src/lib/tunnel/shared/state.js +41 -0
- package/src/lib/tunnel/shared/watchdogConfig.js +8 -0
- package/src/lib/tunnel/tailscale/config.js +7 -0
- package/src/lib/tunnel/tailscale/healthCheck.js +29 -0
- package/src/lib/tunnel/tailscale/manager.js +129 -0
- package/src/lib/tunnel/tailscale/tailscale.js +790 -0
- package/src/lib/updater/updater.js +235 -0
- package/src/lib/usage/fetcher.js +208 -0
- package/src/lib/usageDb.js +7 -0
- package/src/mitm/antigravityIdeVersion.js +50 -0
- package/src/mitm/cert/generate.js +32 -0
- package/src/mitm/cert/install.js +269 -0
- package/src/mitm/cert/rootCA.js +173 -0
- package/src/mitm/config.js +87 -0
- package/src/mitm/dbReader.js +22 -0
- package/src/mitm/dns/dnsConfig.js +266 -0
- package/src/mitm/handlers/antigravity.js +33 -0
- package/src/mitm/handlers/base.js +226 -0
- package/src/mitm/handlers/copilot.js +35 -0
- package/src/mitm/handlers/cursor.js +15 -0
- package/src/mitm/handlers/kiro.js +526 -0
- package/src/mitm/logger.js +106 -0
- package/src/mitm/manager.js +851 -0
- package/src/mitm/paths.js +32 -0
- package/src/mitm/server.js +435 -0
- package/src/mitm/winElevated.js +81 -0
- package/src/models/index.js +38 -0
- package/src/proxy.js +5 -0
- package/src/shared/components/AddCustomEmbeddingModal.js +183 -0
- package/src/shared/components/Avatar.js +88 -0
- package/src/shared/components/Badge.js +54 -0
- package/src/shared/components/BulkAccountAutomationModal.js +508 -0
- package/src/shared/components/Button.js +56 -0
- package/src/shared/components/Card.js +116 -0
- package/src/shared/components/ChangelogModal.js +97 -0
- package/src/shared/components/CodeBuddyQuotaCookieModal.js +109 -0
- package/src/shared/components/ComboFormModal.js +176 -0
- package/src/shared/components/CursorAuthModal.js +212 -0
- package/src/shared/components/DonateModal.js +136 -0
- package/src/shared/components/Drawer.js +82 -0
- package/src/shared/components/EditConnectionModal.js +286 -0
- package/src/shared/components/Footer.js +132 -0
- package/src/shared/components/GitLabAuthModal.js +194 -0
- package/src/shared/components/Header.js +380 -0
- package/src/shared/components/HeaderLanguage.js +46 -0
- package/src/shared/components/HeaderMenu.js +126 -0
- package/src/shared/components/IFlowCookieModal.js +132 -0
- package/src/shared/components/Input.js +65 -0
- package/src/shared/components/KiroAuthModal.js +1171 -0
- package/src/shared/components/KiroOAuthWrapper.js +149 -0
- package/src/shared/components/KiroSocialOAuthModal.js +205 -0
- package/src/shared/components/LanguageSwitcher.js +190 -0
- package/src/shared/components/Loading.js +75 -0
- package/src/shared/components/ManualConfigModal.js +44 -0
- package/src/shared/components/McpMarketplaceModal.js +255 -0
- package/src/shared/components/Modal.js +146 -0
- package/src/shared/components/ModelSelectModal.js +537 -0
- package/src/shared/components/NineRemoteButton.js +23 -0
- package/src/shared/components/NineRemotePromoModal.js +99 -0
- package/src/shared/components/NoAuthProxyCard.js +86 -0
- package/src/shared/components/OAuthModal.js +682 -0
- package/src/shared/components/Pagination.js +150 -0
- package/src/shared/components/PricingModal.js +208 -0
- package/src/shared/components/ProviderIcon.js +63 -0
- package/src/shared/components/ProviderInfoCard.js +82 -0
- package/src/shared/components/RequestLogger.js +121 -0
- package/src/shared/components/SegmentedControl.js +48 -0
- package/src/shared/components/Select.js +67 -0
- package/src/shared/components/Sidebar.js +441 -0
- package/src/shared/components/ThemeProvider.js +15 -0
- package/src/shared/components/ThemeToggle.js +42 -0
- package/src/shared/components/Toggle.js +69 -0
- package/src/shared/components/Tooltip.js +25 -0
- package/src/shared/components/UsageStats.js +505 -0
- package/src/shared/components/index.js +46 -0
- package/src/shared/components/layouts/AuthLayout.js +29 -0
- package/src/shared/components/layouts/DashboardLayout.js +104 -0
- package/src/shared/components/layouts/index.js +4 -0
- package/src/shared/constants/cliTools.js +397 -0
- package/src/shared/constants/colors.js +77 -0
- package/src/shared/constants/config.js +99 -0
- package/src/shared/constants/coworkPlugins.js +75 -0
- package/src/shared/constants/index.js +4 -0
- package/src/shared/constants/locales.js +36 -0
- package/src/shared/constants/mitmToolHosts.js +12 -0
- package/src/shared/constants/models.js +38 -0
- package/src/shared/constants/pricing.js +303 -0
- package/src/shared/constants/providers.js +289 -0
- package/src/shared/constants/skills.js +78 -0
- package/src/shared/constants/ttsProviders.js +138 -0
- package/src/shared/hooks/index.js +2 -0
- package/src/shared/hooks/useCopyToClipboard.js +43 -0
- package/src/shared/hooks/useTheme.js +60 -0
- package/src/shared/services/bootstrap.js +12 -0
- package/src/shared/services/initializeApp.js +268 -0
- package/src/shared/utils/api.js +93 -0
- package/src/shared/utils/apiKey.js +98 -0
- package/src/shared/utils/clineAuth.js +37 -0
- package/src/shared/utils/cn.js +11 -0
- package/src/shared/utils/connectionStatus.js +162 -0
- package/src/shared/utils/index.js +40 -0
- package/src/shared/utils/machine.js +6 -0
- package/src/shared/utils/machineId.js +66 -0
- package/src/shared/utils/providerModelsFetcher.js +30 -0
- package/src/sse/handlers/chat.js +261 -0
- package/src/sse/handlers/embeddings.js +141 -0
- package/src/sse/handlers/fetch.js +213 -0
- package/src/sse/handlers/imageGeneration.js +142 -0
- package/src/sse/handlers/search.js +206 -0
- package/src/sse/handlers/stt.js +88 -0
- package/src/sse/handlers/tts.js +114 -0
- package/src/sse/services/auth.js +346 -0
- package/src/sse/services/codexGateway.js +215 -0
- package/src/sse/services/model.js +99 -0
- package/src/sse/services/tokenRefresh.js +319 -0
- package/src/sse/utils/logger.js +75 -0
- package/src/store/headerSearchStore.js +19 -0
- package/src/store/index.js +6 -0
- package/src/store/notificationStore.js +45 -0
- package/src/store/providerStore.js +55 -0
- package/src/store/settingsStore.js +51 -0
- package/src/store/themeStore.js +54 -0
- package/src/store/userStore.js +20 -0
- package/start.sh +4 -0
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
|
4
|
+
import { Badge, Button, Card, CardSkeleton, Input, Modal, Toggle, ConfirmModal, Pagination } from "@/shared/components";
|
|
5
|
+
import { useNotificationStore } from "@/store/notificationStore";
|
|
6
|
+
|
|
7
|
+
function getStatusVariant(status) {
|
|
8
|
+
if (status === "active") return "success";
|
|
9
|
+
if (status === "error") return "error";
|
|
10
|
+
return "default";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function formatDateTime(value) {
|
|
14
|
+
if (!value) return "Never";
|
|
15
|
+
const date = new Date(value);
|
|
16
|
+
if (Number.isNaN(date.getTime())) return "Never";
|
|
17
|
+
return date.toLocaleString();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeFormData(data = {}) {
|
|
21
|
+
return {
|
|
22
|
+
name: data.name || "",
|
|
23
|
+
proxyUrl: data.proxyUrl || "",
|
|
24
|
+
noProxy: data.noProxy || "",
|
|
25
|
+
isActive: data.isActive !== false,
|
|
26
|
+
strictProxy: data.strictProxy === true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const PROXY_POOL_DEFAULT_PAGE_SIZE = 20;
|
|
31
|
+
|
|
32
|
+
export default function ProxyPoolsPage() {
|
|
33
|
+
const [proxyPools, setProxyPools] = useState([]);
|
|
34
|
+
const [loading, setLoading] = useState(true);
|
|
35
|
+
const [showFormModal, setShowFormModal] = useState(false);
|
|
36
|
+
const [showBatchImportModal, setShowBatchImportModal] = useState(false);
|
|
37
|
+
const [showVercelModal, setShowVercelModal] = useState(false);
|
|
38
|
+
const [showCloudflareModal, setShowCloudflareModal] = useState(false);
|
|
39
|
+
const [showDenoModal, setShowDenoModal] = useState(false);
|
|
40
|
+
const [showRelayMenu, setShowRelayMenu] = useState(false);
|
|
41
|
+
const [editingProxyPool, setEditingProxyPool] = useState(null);
|
|
42
|
+
const [formData, setFormData] = useState(normalizeFormData());
|
|
43
|
+
const [batchImportText, setBatchImportText] = useState("");
|
|
44
|
+
const [vercelForm, setVercelForm] = useState({ vercelToken: "", projectName: "vercel-relay" });
|
|
45
|
+
const [cloudflareForm, setCloudflareForm] = useState({ accountId: "", apiToken: "", projectName: "cloudflare-relay" });
|
|
46
|
+
const [denoForm, setDenoForm] = useState({ denoToken: "", orgDomain: "", projectName: "" });
|
|
47
|
+
const [saving, setSaving] = useState(false);
|
|
48
|
+
const [importing, setImporting] = useState(false);
|
|
49
|
+
const [deploying, setDeploying] = useState(false);
|
|
50
|
+
const [testingId, setTestingId] = useState(null);
|
|
51
|
+
const [selectedIds, setSelectedIds] = useState([]);
|
|
52
|
+
const [healthChecking, setHealthChecking] = useState(false);
|
|
53
|
+
const [healthProgress, setHealthProgress] = useState({ current: 0, total: 0 });
|
|
54
|
+
const [bulkBusy, setBulkBusy] = useState(false);
|
|
55
|
+
const [confirmState, setConfirmState] = useState(null);
|
|
56
|
+
const [page, setPage] = useState(1);
|
|
57
|
+
const [pageSize, setPageSize] = useState(PROXY_POOL_DEFAULT_PAGE_SIZE);
|
|
58
|
+
const relayMenuRef = useRef(null);
|
|
59
|
+
const notify = useNotificationStore();
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const handleClickOutside = (e) => {
|
|
63
|
+
if (relayMenuRef.current && !relayMenuRef.current.contains(e.target)) {
|
|
64
|
+
setShowRelayMenu(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
if (showRelayMenu) {
|
|
68
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
69
|
+
}
|
|
70
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
71
|
+
}, [showRelayMenu]);
|
|
72
|
+
|
|
73
|
+
const fetchProxyPools = useCallback(async () => {
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch("/api/proxy-pools?includeUsage=true", { cache: "no-store" });
|
|
76
|
+
const data = await res.json();
|
|
77
|
+
if (res.ok) {
|
|
78
|
+
setProxyPools(data.proxyPools || []);
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.log("Error fetching proxy pools:", error);
|
|
82
|
+
} finally {
|
|
83
|
+
setLoading(false);
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
fetchProxyPools();
|
|
89
|
+
}, [fetchProxyPools]);
|
|
90
|
+
|
|
91
|
+
const resetForm = () => {
|
|
92
|
+
setEditingProxyPool(null);
|
|
93
|
+
setFormData(normalizeFormData());
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const openCreateModal = () => {
|
|
97
|
+
resetForm();
|
|
98
|
+
setShowFormModal(true);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const openEditModal = (proxyPool) => {
|
|
102
|
+
setEditingProxyPool(proxyPool);
|
|
103
|
+
setFormData(normalizeFormData(proxyPool));
|
|
104
|
+
setShowFormModal(true);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const closeFormModal = () => {
|
|
108
|
+
setShowFormModal(false);
|
|
109
|
+
resetForm();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleSave = async () => {
|
|
113
|
+
const payload = {
|
|
114
|
+
name: formData.name.trim(),
|
|
115
|
+
proxyUrl: formData.proxyUrl.trim(),
|
|
116
|
+
noProxy: formData.noProxy.trim(),
|
|
117
|
+
isActive: formData.isActive === true,
|
|
118
|
+
strictProxy: formData.strictProxy === true,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (!payload.name || !payload.proxyUrl) return;
|
|
122
|
+
|
|
123
|
+
setSaving(true);
|
|
124
|
+
try {
|
|
125
|
+
const isEdit = !!editingProxyPool;
|
|
126
|
+
const res = await fetch(isEdit ? `/api/proxy-pools/${editingProxyPool.id}` : "/api/proxy-pools", {
|
|
127
|
+
method: isEdit ? "PUT" : "POST",
|
|
128
|
+
headers: { "Content-Type": "application/json" },
|
|
129
|
+
body: JSON.stringify(payload),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (res.ok) {
|
|
133
|
+
await fetchProxyPools();
|
|
134
|
+
closeFormModal();
|
|
135
|
+
notify.success(editingProxyPool ? "Proxy pool updated" : "Proxy pool created");
|
|
136
|
+
} else {
|
|
137
|
+
const data = await res.json();
|
|
138
|
+
notify.error(data.error || "Failed to save proxy pool");
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.log("Error saving proxy pool:", error);
|
|
142
|
+
} finally {
|
|
143
|
+
setSaving(false);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const handleDelete = async (proxyPool) => {
|
|
148
|
+
setConfirmState({
|
|
149
|
+
title: "Delete Proxy Pool",
|
|
150
|
+
message: `Delete proxy pool "${proxyPool.name}"?`,
|
|
151
|
+
onConfirm: async () => {
|
|
152
|
+
setConfirmState(null);
|
|
153
|
+
try {
|
|
154
|
+
const res = await fetch(`/api/proxy-pools/${proxyPool.id}`, { method: "DELETE" });
|
|
155
|
+
if (res.ok) {
|
|
156
|
+
setProxyPools((prev) => prev.filter((item) => item.id !== proxyPool.id));
|
|
157
|
+
notify.success("Proxy pool deleted");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const data = await res.json();
|
|
162
|
+
if (res.status === 409) {
|
|
163
|
+
notify.warning(`Cannot delete: ${data.boundConnectionCount || 0} connection(s) are still using this pool.`);
|
|
164
|
+
} else {
|
|
165
|
+
notify.error(data.error || "Failed to delete proxy pool");
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.log("Error deleting proxy pool:", error);
|
|
169
|
+
notify.error("Failed to delete proxy pool");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const handleTest = async (proxyPoolId) => {
|
|
176
|
+
setTestingId(proxyPoolId);
|
|
177
|
+
try {
|
|
178
|
+
const res = await fetch(`/api/proxy-pools/${proxyPoolId}/test`, { method: "POST" });
|
|
179
|
+
const data = await res.json();
|
|
180
|
+
|
|
181
|
+
if (!res.ok) {
|
|
182
|
+
notify.error(data.error || "Failed to test proxy");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await fetchProxyPools();
|
|
187
|
+
notify.success(data.ok ? "Proxy test passed" : "Proxy test failed");
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.log("Error testing proxy pool:", error);
|
|
190
|
+
notify.error("Failed to test proxy");
|
|
191
|
+
} finally {
|
|
192
|
+
setTestingId(null);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const handleToggleActive = async (pool) => {
|
|
197
|
+
const next = !pool.isActive;
|
|
198
|
+
setProxyPools((prev) => prev.map((p) => p.id === pool.id ? { ...p, isActive: next } : p));
|
|
199
|
+
try {
|
|
200
|
+
const res = await fetch(`/api/proxy-pools/${pool.id}`, {
|
|
201
|
+
method: "PUT",
|
|
202
|
+
headers: { "Content-Type": "application/json" },
|
|
203
|
+
body: JSON.stringify({ isActive: next }),
|
|
204
|
+
});
|
|
205
|
+
if (!res.ok) {
|
|
206
|
+
setProxyPools((prev) => prev.map((p) => p.id === pool.id ? { ...p, isActive: pool.isActive } : p));
|
|
207
|
+
notify.error("Failed to update active state");
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.log("Error toggling active:", error);
|
|
211
|
+
setProxyPools((prev) => prev.map((p) => p.id === pool.id ? { ...p, isActive: pool.isActive } : p));
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const totalPages = Math.max(1, Math.ceil(proxyPools.length / pageSize));
|
|
216
|
+
const activePage = Math.min(page, totalPages);
|
|
217
|
+
const paginatedProxyPools = proxyPools.slice(
|
|
218
|
+
(activePage - 1) * pageSize,
|
|
219
|
+
activePage * pageSize
|
|
220
|
+
);
|
|
221
|
+
const allSelected = paginatedProxyPools.length > 0 && paginatedProxyPools.every((pool) => selectedIds.includes(pool.id));
|
|
222
|
+
const toggleSelect = (id) => setSelectedIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]);
|
|
223
|
+
const toggleSelectAll = () => setSelectedIds((prev) => {
|
|
224
|
+
if (allSelected) return prev.filter((id) => !paginatedProxyPools.some((pool) => pool.id === id));
|
|
225
|
+
return Array.from(new Set([...prev, ...paginatedProxyPools.map((pool) => pool.id)]));
|
|
226
|
+
});
|
|
227
|
+
const clearSelection = () => setSelectedIds([]);
|
|
228
|
+
|
|
229
|
+
const bulkSetActive = async (isActive) => {
|
|
230
|
+
const targets = selectedIds.length > 0 ? selectedIds : proxyPools.map((p) => p.id);
|
|
231
|
+
if (targets.length === 0) return;
|
|
232
|
+
setBulkBusy(true);
|
|
233
|
+
try {
|
|
234
|
+
let ok = 0; let failed = 0;
|
|
235
|
+
for (const id of targets) {
|
|
236
|
+
try {
|
|
237
|
+
const res = await fetch(`/api/proxy-pools/${id}`, {
|
|
238
|
+
method: "PUT",
|
|
239
|
+
headers: { "Content-Type": "application/json" },
|
|
240
|
+
body: JSON.stringify({ isActive }),
|
|
241
|
+
});
|
|
242
|
+
if (res.ok) ok += 1; else failed += 1;
|
|
243
|
+
} catch { failed += 1; }
|
|
244
|
+
}
|
|
245
|
+
await fetchProxyPools();
|
|
246
|
+
notify.success(`${isActive ? "Activated" : "Deactivated"} ${ok}${failed ? `, failed ${failed}` : ""}`);
|
|
247
|
+
} finally {
|
|
248
|
+
setBulkBusy(false);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const bulkDelete = async () => {
|
|
253
|
+
if (selectedIds.length === 0) return;
|
|
254
|
+
setConfirmState({
|
|
255
|
+
title: "Delete Proxy Pools",
|
|
256
|
+
message: `Delete ${selectedIds.length} proxy pool(s)?`,
|
|
257
|
+
onConfirm: async () => {
|
|
258
|
+
setConfirmState(null);
|
|
259
|
+
setBulkBusy(true);
|
|
260
|
+
try {
|
|
261
|
+
let ok = 0; let blocked = 0; let failed = 0;
|
|
262
|
+
for (const id of selectedIds) {
|
|
263
|
+
try {
|
|
264
|
+
const res = await fetch(`/api/proxy-pools/${id}`, { method: "DELETE" });
|
|
265
|
+
if (res.ok) ok += 1;
|
|
266
|
+
else if (res.status === 409) blocked += 1;
|
|
267
|
+
else failed += 1;
|
|
268
|
+
} catch { failed += 1; }
|
|
269
|
+
}
|
|
270
|
+
await fetchProxyPools();
|
|
271
|
+
clearSelection();
|
|
272
|
+
notify.success(`Deleted ${ok}${blocked ? `, ${blocked} bound` : ""}${failed ? `, ${failed} failed` : ""}`);
|
|
273
|
+
} finally {
|
|
274
|
+
setBulkBusy(false);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const handleHealthCheck = async () => {
|
|
281
|
+
const targets = selectedIds.length > 0
|
|
282
|
+
? proxyPools.filter((p) => selectedIds.includes(p.id))
|
|
283
|
+
: proxyPools;
|
|
284
|
+
if (targets.length === 0) return;
|
|
285
|
+
setHealthChecking(true);
|
|
286
|
+
setHealthProgress({ current: 0, total: targets.length });
|
|
287
|
+
let alive = 0; const deadIds = [];
|
|
288
|
+
let done = 0;
|
|
289
|
+
const CONCURRENCY = 10;
|
|
290
|
+
const queue = [...targets];
|
|
291
|
+
|
|
292
|
+
const worker = async () => {
|
|
293
|
+
while (queue.length > 0) {
|
|
294
|
+
const pool = queue.shift();
|
|
295
|
+
if (!pool) break;
|
|
296
|
+
try {
|
|
297
|
+
const res = await fetch(`/api/proxy-pools/${pool.id}/test`, { method: "POST" });
|
|
298
|
+
const data = await res.json();
|
|
299
|
+
if (res.ok && data.ok) alive += 1; else deadIds.push(pool.id);
|
|
300
|
+
} catch {
|
|
301
|
+
deadIds.push(pool.id);
|
|
302
|
+
} finally {
|
|
303
|
+
done += 1;
|
|
304
|
+
setHealthProgress({ current: done, total: targets.length });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, targets.length) }, worker));
|
|
310
|
+
await fetchProxyPools();
|
|
311
|
+
setHealthChecking(false);
|
|
312
|
+
setHealthProgress({ current: 0, total: 0 });
|
|
313
|
+
|
|
314
|
+
if (deadIds.length > 0) {
|
|
315
|
+
setConfirmState({
|
|
316
|
+
title: "Disable Dead Proxies",
|
|
317
|
+
message: `Alive: ${alive}, Dead: ${deadIds.length}.\n\nDisable ${deadIds.length} dead proxies?`,
|
|
318
|
+
onConfirm: async () => {
|
|
319
|
+
setConfirmState(null);
|
|
320
|
+
setBulkBusy(true);
|
|
321
|
+
try {
|
|
322
|
+
for (const id of deadIds) {
|
|
323
|
+
try {
|
|
324
|
+
await fetch(`/api/proxy-pools/${id}`, {
|
|
325
|
+
method: "PUT",
|
|
326
|
+
headers: { "Content-Type": "application/json" },
|
|
327
|
+
body: JSON.stringify({ isActive: false }),
|
|
328
|
+
});
|
|
329
|
+
} catch {}
|
|
330
|
+
}
|
|
331
|
+
await fetchProxyPools();
|
|
332
|
+
notify.success(`Disabled ${deadIds.length} dead proxies`);
|
|
333
|
+
} finally {
|
|
334
|
+
setBulkBusy(false);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
} else {
|
|
339
|
+
notify.success(`Health check done. Alive: ${alive}, Dead: ${deadIds.length}`);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Cleanup selectedIds when pools change
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
setSelectedIds((prev) => prev.filter((id) => proxyPools.some((p) => p.id === id)));
|
|
346
|
+
}, [proxyPools]);
|
|
347
|
+
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
setPage((current) => Math.min(current, Math.max(1, Math.ceil(proxyPools.length / pageSize))));
|
|
350
|
+
}, [proxyPools.length, pageSize]);
|
|
351
|
+
|
|
352
|
+
const openBatchImportModal = () => {
|
|
353
|
+
setBatchImportText("");
|
|
354
|
+
setShowBatchImportModal(true);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const closeBatchImportModal = () => {
|
|
358
|
+
if (importing) return;
|
|
359
|
+
setShowBatchImportModal(false);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const openVercelModal = () => {
|
|
363
|
+
setVercelForm({ vercelToken: "", projectName: "vercel-relay" });
|
|
364
|
+
setShowVercelModal(true);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const closeVercelModal = () => {
|
|
368
|
+
if (deploying) return;
|
|
369
|
+
setShowVercelModal(false);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const openCloudflareModal = () => {
|
|
373
|
+
setCloudflareForm({ accountId: "", apiToken: "", projectName: "cloudflare-relay" });
|
|
374
|
+
setShowCloudflareModal(true);
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const closeCloudflareModal = () => {
|
|
378
|
+
if (deploying) return;
|
|
379
|
+
setShowCloudflareModal(false);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const openDenoModal = () => {
|
|
383
|
+
setDenoForm({ denoToken: "", orgDomain: "", projectName: "" });
|
|
384
|
+
setShowDenoModal(true);
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const closeDenoModal = () => {
|
|
388
|
+
if (deploying) return;
|
|
389
|
+
setShowDenoModal(false);
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const handleVercelDeploy = async () => {
|
|
393
|
+
if (!vercelForm.vercelToken.trim()) return;
|
|
394
|
+
setDeploying(true);
|
|
395
|
+
try {
|
|
396
|
+
const res = await fetch("/api/proxy-pools/vercel-deploy", {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: { "Content-Type": "application/json" },
|
|
399
|
+
body: JSON.stringify(vercelForm),
|
|
400
|
+
});
|
|
401
|
+
const data = await res.json();
|
|
402
|
+
if (res.ok) {
|
|
403
|
+
await fetchProxyPools();
|
|
404
|
+
closeVercelModal();
|
|
405
|
+
notify.success(`Deployed: ${data.deployUrl}`);
|
|
406
|
+
} else {
|
|
407
|
+
notify.error(data.error || "Deploy failed");
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.log("Error deploying Vercel relay:", error);
|
|
411
|
+
notify.error("Deploy failed");
|
|
412
|
+
} finally {
|
|
413
|
+
setDeploying(false);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const handleCloudflareDeploy = async () => {
|
|
418
|
+
if (!cloudflareForm.accountId.trim() || !cloudflareForm.apiToken.trim()) return;
|
|
419
|
+
setDeploying(true);
|
|
420
|
+
try {
|
|
421
|
+
const res = await fetch("/api/proxy-pools/cloudflare-deploy", {
|
|
422
|
+
method: "POST",
|
|
423
|
+
headers: { "Content-Type": "application/json" },
|
|
424
|
+
body: JSON.stringify(cloudflareForm),
|
|
425
|
+
});
|
|
426
|
+
const data = await res.json();
|
|
427
|
+
if (res.ok) {
|
|
428
|
+
await fetchProxyPools();
|
|
429
|
+
closeCloudflareModal();
|
|
430
|
+
notify.success(`Deployed: ${data.deployUrl}`);
|
|
431
|
+
} else {
|
|
432
|
+
notify.error(data.error || "Deploy failed");
|
|
433
|
+
}
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.log("Error deploying Cloudflare relay:", error);
|
|
436
|
+
notify.error("Deploy failed");
|
|
437
|
+
} finally {
|
|
438
|
+
setDeploying(false);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const handleDenoDeploy = async () => {
|
|
443
|
+
if (!denoForm.denoToken.trim()) return;
|
|
444
|
+
setDeploying(true);
|
|
445
|
+
try {
|
|
446
|
+
const res = await fetch("/api/proxy-pools/deno-deploy", {
|
|
447
|
+
method: "POST",
|
|
448
|
+
headers: { "Content-Type": "application/json" },
|
|
449
|
+
body: JSON.stringify(denoForm),
|
|
450
|
+
});
|
|
451
|
+
const data = await res.json();
|
|
452
|
+
if (res.ok) {
|
|
453
|
+
await fetchProxyPools();
|
|
454
|
+
closeDenoModal();
|
|
455
|
+
notify.success(`Deployed: ${data.deployUrl}`);
|
|
456
|
+
} else {
|
|
457
|
+
notify.error(data.error || "Deploy failed");
|
|
458
|
+
}
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.log("Error deploying Deno relay:", error);
|
|
461
|
+
notify.error("Deploy failed");
|
|
462
|
+
} finally {
|
|
463
|
+
setDeploying(false);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const parseProxyLine = (line) => {
|
|
468
|
+
const trimmed = line.trim();
|
|
469
|
+
if (!trimmed) return null;
|
|
470
|
+
|
|
471
|
+
if (trimmed.includes("://")) {
|
|
472
|
+
const parsed = new URL(trimmed);
|
|
473
|
+
const hostLabel = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname;
|
|
474
|
+
return {
|
|
475
|
+
proxyUrl: parsed.toString(),
|
|
476
|
+
name: `Imported ${hostLabel}`,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const parts = trimmed.split(":");
|
|
481
|
+
if (parts.length === 4) {
|
|
482
|
+
const [host, port, username, password] = parts;
|
|
483
|
+
if (!host || !port || !username || !password) {
|
|
484
|
+
throw new Error("Invalid host:port:user:pass format");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const proxyUrl = `http://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}`;
|
|
488
|
+
const parsed = new URL(proxyUrl);
|
|
489
|
+
return {
|
|
490
|
+
proxyUrl: parsed.toString(),
|
|
491
|
+
name: `Imported ${host}:${port}`,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
throw new Error("Unsupported format");
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const handleBatchImport = async () => {
|
|
499
|
+
const lines = batchImportText
|
|
500
|
+
.split(/\r?\n/)
|
|
501
|
+
.map((line) => line.trim())
|
|
502
|
+
.filter(Boolean);
|
|
503
|
+
|
|
504
|
+
if (lines.length === 0) {
|
|
505
|
+
notify.warning("Please paste at least one proxy line.");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const parsedEntries = [];
|
|
510
|
+
const invalidLines = [];
|
|
511
|
+
|
|
512
|
+
lines.forEach((line, index) => {
|
|
513
|
+
try {
|
|
514
|
+
const parsed = parseProxyLine(line);
|
|
515
|
+
if (parsed) {
|
|
516
|
+
parsedEntries.push({
|
|
517
|
+
...parsed,
|
|
518
|
+
lineNumber: index + 1,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
} catch (error) {
|
|
522
|
+
invalidLines.push(`Line ${index + 1}: ${error.message}`);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
if (invalidLines.length > 0) {
|
|
527
|
+
notify.error(`Invalid proxy format:\n${invalidLines.join("\n")}`);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
setImporting(true);
|
|
532
|
+
try {
|
|
533
|
+
const existingKeys = new Set(
|
|
534
|
+
proxyPools.map((pool) => `${(pool.proxyUrl || "").trim()}|||${(pool.noProxy || "").trim()}`)
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
let created = 0;
|
|
538
|
+
let skipped = 0;
|
|
539
|
+
let failed = 0;
|
|
540
|
+
|
|
541
|
+
for (const entry of parsedEntries) {
|
|
542
|
+
const dedupeKey = `${entry.proxyUrl}|||`;
|
|
543
|
+
if (existingKeys.has(dedupeKey)) {
|
|
544
|
+
skipped += 1;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const res = await fetch("/api/proxy-pools", {
|
|
549
|
+
method: "POST",
|
|
550
|
+
headers: { "Content-Type": "application/json" },
|
|
551
|
+
body: JSON.stringify({
|
|
552
|
+
name: entry.name,
|
|
553
|
+
proxyUrl: entry.proxyUrl,
|
|
554
|
+
noProxy: "",
|
|
555
|
+
isActive: true,
|
|
556
|
+
}),
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
if (res.ok) {
|
|
560
|
+
created += 1;
|
|
561
|
+
existingKeys.add(dedupeKey);
|
|
562
|
+
} else {
|
|
563
|
+
failed += 1;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
await fetchProxyPools();
|
|
568
|
+
setShowBatchImportModal(false);
|
|
569
|
+
notify.success(`Batch import completed: Created ${created}, Skipped ${skipped}, Failed ${failed}`);
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.log("Error batch importing proxies:", error);
|
|
572
|
+
notify.error("Batch import failed");
|
|
573
|
+
} finally {
|
|
574
|
+
setImporting(false);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const activeCount = useMemo(
|
|
579
|
+
() => proxyPools.filter((pool) => pool.isActive === true).length,
|
|
580
|
+
[proxyPools]
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
if (loading) {
|
|
584
|
+
return (
|
|
585
|
+
<div className="mx-auto flex w-full max-w-5xl flex-col gap-4 px-1 sm:gap-6 sm:px-0">
|
|
586
|
+
<CardSkeleton />
|
|
587
|
+
<CardSkeleton />
|
|
588
|
+
</div>
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return (
|
|
593
|
+
<div className="mx-auto flex w-full max-w-5xl flex-col gap-4 px-1 sm:gap-6 sm:px-0">
|
|
594
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
595
|
+
<div className="min-w-0">
|
|
596
|
+
<h1 className="text-xl font-semibold sm:text-2xl">Proxy Pools</h1>
|
|
597
|
+
</div>
|
|
598
|
+
|
|
599
|
+
<div className="grid grid-cols-1 gap-2 sm:flex sm:items-center">
|
|
600
|
+
<div className="relative" ref={relayMenuRef}>
|
|
601
|
+
<Button
|
|
602
|
+
size="sm"
|
|
603
|
+
variant="secondary"
|
|
604
|
+
icon="rocket_launch"
|
|
605
|
+
onClick={() => setShowRelayMenu(!showRelayMenu)}
|
|
606
|
+
>
|
|
607
|
+
Deploy Relay
|
|
608
|
+
<span className="material-symbols-outlined ml-1 text-[18px]">
|
|
609
|
+
{showRelayMenu ? "expand_less" : "expand_more"}
|
|
610
|
+
</span>
|
|
611
|
+
</Button>
|
|
612
|
+
|
|
613
|
+
{showRelayMenu && (
|
|
614
|
+
<div className="absolute left-0 top-full z-50 mt-1 w-48 rounded-xl border border-black/10 bg-white p-1 shadow-xl dark:border-white/10 dark:bg-zinc-900 sm:left-auto sm:right-0">
|
|
615
|
+
<button
|
|
616
|
+
onClick={() => {
|
|
617
|
+
openCloudflareModal();
|
|
618
|
+
setShowRelayMenu(false);
|
|
619
|
+
}}
|
|
620
|
+
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
|
|
621
|
+
>
|
|
622
|
+
<span className="material-symbols-outlined text-[20px] text-orange-500">cloud</span>
|
|
623
|
+
Cloudflare Relay
|
|
624
|
+
</button>
|
|
625
|
+
<button
|
|
626
|
+
onClick={() => {
|
|
627
|
+
openVercelModal();
|
|
628
|
+
setShowRelayMenu(false);
|
|
629
|
+
}}
|
|
630
|
+
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
|
|
631
|
+
>
|
|
632
|
+
<span className="material-symbols-outlined text-[20px] text-blue-500">cloud_upload</span>
|
|
633
|
+
Vercel Relay
|
|
634
|
+
</button>
|
|
635
|
+
<button
|
|
636
|
+
onClick={() => {
|
|
637
|
+
openDenoModal();
|
|
638
|
+
setShowRelayMenu(false);
|
|
639
|
+
}}
|
|
640
|
+
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
|
|
641
|
+
>
|
|
642
|
+
<span className="material-symbols-outlined text-[20px] text-green-500">terminal</span>
|
|
643
|
+
Deno Relay
|
|
644
|
+
</button>
|
|
645
|
+
</div>
|
|
646
|
+
)}
|
|
647
|
+
</div>
|
|
648
|
+
|
|
649
|
+
<Button size="sm" variant="secondary" icon="upload" onClick={openBatchImportModal}>
|
|
650
|
+
Batch Import
|
|
651
|
+
</Button>
|
|
652
|
+
<Button size="sm" icon="add" onClick={openCreateModal}>Add Proxy Pool</Button>
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
<Card>
|
|
657
|
+
<div className="mb-4 flex flex-wrap items-center gap-2">
|
|
658
|
+
{proxyPools.length > 0 && (
|
|
659
|
+
<label className="flex items-center gap-1.5 text-xs text-text-muted cursor-pointer">
|
|
660
|
+
<input
|
|
661
|
+
type="checkbox"
|
|
662
|
+
checked={allSelected}
|
|
663
|
+
onChange={toggleSelectAll}
|
|
664
|
+
className="size-4 rounded border-black/20 dark:border-white/20"
|
|
665
|
+
/>
|
|
666
|
+
{allSelected ? "Unselect visible" : "Select visible"}
|
|
667
|
+
</label>
|
|
668
|
+
)}
|
|
669
|
+
<Badge variant="default">Total: {proxyPools.length}</Badge>
|
|
670
|
+
<Badge variant="success">Active: {activeCount}</Badge>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
{(selectedIds.length > 0 || healthChecking) && (
|
|
674
|
+
<div className="mb-4 flex flex-wrap items-center gap-2 rounded-lg border border-primary/30 bg-primary/5 px-3 py-2">
|
|
675
|
+
<span className="material-symbols-outlined text-[18px] text-primary">checklist</span>
|
|
676
|
+
<span className="text-xs font-medium text-primary">
|
|
677
|
+
{selectedIds.length > 0 ? `${selectedIds.length} selected` : "All pools"}
|
|
678
|
+
</span>
|
|
679
|
+
<div className="ml-auto flex flex-wrap items-center gap-2">
|
|
680
|
+
<Button
|
|
681
|
+
size="sm"
|
|
682
|
+
icon={healthChecking ? "progress_activity" : "health_and_safety"}
|
|
683
|
+
onClick={handleHealthCheck}
|
|
684
|
+
disabled={healthChecking || bulkBusy || proxyPools.length === 0}
|
|
685
|
+
>
|
|
686
|
+
{healthChecking ? `Checking ${healthProgress.current}/${healthProgress.total}` : "Health Check"}
|
|
687
|
+
</Button>
|
|
688
|
+
{selectedIds.length > 0 && (
|
|
689
|
+
<>
|
|
690
|
+
<Button size="sm" variant="secondary" icon="toggle_on" onClick={() => bulkSetActive(true)} disabled={bulkBusy || healthChecking}>
|
|
691
|
+
Activate
|
|
692
|
+
</Button>
|
|
693
|
+
<Button size="sm" variant="secondary" icon="toggle_off" onClick={() => bulkSetActive(false)} disabled={bulkBusy || healthChecking}>
|
|
694
|
+
Deactivate
|
|
695
|
+
</Button>
|
|
696
|
+
<Button size="sm" variant="secondary" icon="delete" onClick={bulkDelete} disabled={bulkBusy || healthChecking}>
|
|
697
|
+
Delete
|
|
698
|
+
</Button>
|
|
699
|
+
<Button size="sm" variant="ghost" onClick={clearSelection} disabled={bulkBusy || healthChecking}>
|
|
700
|
+
Clear
|
|
701
|
+
</Button>
|
|
702
|
+
</>
|
|
703
|
+
)}
|
|
704
|
+
</div>
|
|
705
|
+
</div>
|
|
706
|
+
)}
|
|
707
|
+
|
|
708
|
+
{proxyPools.length === 0 ? (
|
|
709
|
+
<div className="text-center py-10">
|
|
710
|
+
<p className="text-text-main font-medium mb-1">No proxy pool entries yet</p>
|
|
711
|
+
<p className="text-sm text-text-muted mb-4">
|
|
712
|
+
Create a proxy pool entry, then assign it to connections.
|
|
713
|
+
</p>
|
|
714
|
+
<Button icon="add" onClick={openCreateModal}>Add Proxy Pool</Button>
|
|
715
|
+
</div>
|
|
716
|
+
) : (
|
|
717
|
+
<div className="flex flex-col divide-y divide-black/[0.04] dark:divide-white/[0.05]">
|
|
718
|
+
{paginatedProxyPools.map((pool) => (
|
|
719
|
+
<div key={pool.id} className="flex flex-col gap-3 py-3 sm:flex-row sm:items-center sm:justify-between">
|
|
720
|
+
<div className="flex items-start gap-3 min-w-0 flex-1">
|
|
721
|
+
<input
|
|
722
|
+
type="checkbox"
|
|
723
|
+
checked={selectedIds.includes(pool.id)}
|
|
724
|
+
onChange={() => toggleSelect(pool.id)}
|
|
725
|
+
className="mt-1 size-4 shrink-0 rounded border-black/20 dark:border-white/20"
|
|
726
|
+
/>
|
|
727
|
+
<div className="min-w-0 flex-1">
|
|
728
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
729
|
+
<p className="min-w-0 max-w-full truncate text-sm font-medium sm:max-w-[18rem]">{pool.name}</p>
|
|
730
|
+
<Badge variant={getStatusVariant(pool.testStatus)} size="sm" dot>
|
|
731
|
+
{pool.testStatus || "unknown"}
|
|
732
|
+
</Badge>
|
|
733
|
+
<Badge variant={pool.isActive ? "success" : "default"} size="sm">
|
|
734
|
+
{pool.isActive ? "active" : "inactive"}
|
|
735
|
+
</Badge>
|
|
736
|
+
{pool.type === "vercel" && (
|
|
737
|
+
<Badge variant="default" size="sm">vercel relay</Badge>
|
|
738
|
+
)}
|
|
739
|
+
{pool.type === "cloudflare" && (
|
|
740
|
+
<Badge variant="default" size="sm">cloudflare relay</Badge>
|
|
741
|
+
)}
|
|
742
|
+
<Badge variant="default" size="sm">
|
|
743
|
+
{pool.boundConnectionCount || 0} bound
|
|
744
|
+
</Badge>
|
|
745
|
+
</div>
|
|
746
|
+
<p className="text-xs text-text-muted truncate mt-1">{pool.proxyUrl}</p>
|
|
747
|
+
{pool.noProxy ? (
|
|
748
|
+
<p className="text-xs text-text-muted truncate">No proxy: {pool.noProxy}</p>
|
|
749
|
+
) : null}
|
|
750
|
+
<p className="text-[11px] text-text-muted mt-1">
|
|
751
|
+
Last tested: {formatDateTime(pool.lastTestedAt)}
|
|
752
|
+
{pool.lastError ? ` · ${pool.lastError}` : ""}
|
|
753
|
+
</p>
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
|
|
757
|
+
<div className="flex items-center justify-end gap-1">
|
|
758
|
+
<Toggle
|
|
759
|
+
size="sm"
|
|
760
|
+
checked={pool.isActive === true}
|
|
761
|
+
onChange={() => handleToggleActive(pool)}
|
|
762
|
+
title={pool.isActive ? "Disable" : "Enable"}
|
|
763
|
+
/>
|
|
764
|
+
<button
|
|
765
|
+
onClick={() => handleTest(pool.id)}
|
|
766
|
+
className="p-2 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-primary"
|
|
767
|
+
title="Test proxy"
|
|
768
|
+
disabled={testingId === pool.id}
|
|
769
|
+
>
|
|
770
|
+
<span
|
|
771
|
+
className="material-symbols-outlined text-[18px]"
|
|
772
|
+
style={testingId === pool.id ? { animation: "spin 1s linear infinite" } : undefined}
|
|
773
|
+
>
|
|
774
|
+
{testingId === pool.id ? "progress_activity" : "science"}
|
|
775
|
+
</span>
|
|
776
|
+
</button>
|
|
777
|
+
<button
|
|
778
|
+
onClick={() => openEditModal(pool)}
|
|
779
|
+
className="p-2 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-primary"
|
|
780
|
+
title="Edit"
|
|
781
|
+
>
|
|
782
|
+
<span className="material-symbols-outlined text-[18px]">edit</span>
|
|
783
|
+
</button>
|
|
784
|
+
<button
|
|
785
|
+
onClick={() => handleDelete(pool)}
|
|
786
|
+
className="p-2 rounded hover:bg-red-500/10 text-red-500"
|
|
787
|
+
title="Delete"
|
|
788
|
+
>
|
|
789
|
+
<span className="material-symbols-outlined text-[18px]">delete</span>
|
|
790
|
+
</button>
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
))}
|
|
794
|
+
{proxyPools.length > 0 && (
|
|
795
|
+
<Pagination
|
|
796
|
+
currentPage={activePage}
|
|
797
|
+
pageSize={pageSize}
|
|
798
|
+
totalItems={proxyPools.length}
|
|
799
|
+
onPageChange={setPage}
|
|
800
|
+
onPageSizeChange={(size) => {
|
|
801
|
+
setPageSize(size);
|
|
802
|
+
setPage(1);
|
|
803
|
+
}}
|
|
804
|
+
/>
|
|
805
|
+
)}
|
|
806
|
+
</div>
|
|
807
|
+
)}
|
|
808
|
+
</Card>
|
|
809
|
+
|
|
810
|
+
<Modal
|
|
811
|
+
isOpen={showBatchImportModal}
|
|
812
|
+
title="Batch Import Proxies"
|
|
813
|
+
onClose={closeBatchImportModal}
|
|
814
|
+
>
|
|
815
|
+
<div className="flex flex-col gap-4">
|
|
816
|
+
<div>
|
|
817
|
+
<label className="text-sm font-medium text-text-main mb-1 block">Paste Proxy List (One per line)</label>
|
|
818
|
+
<textarea
|
|
819
|
+
value={batchImportText}
|
|
820
|
+
onChange={(e) => setBatchImportText(e.target.value)}
|
|
821
|
+
placeholder={"http://user:pass@127.0.0.1:7897\n127.0.0.1:7897:user:pass"}
|
|
822
|
+
className="w-full min-h-[180px] py-2 px-3 text-sm text-text-main bg-white dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-md focus:ring-1 focus:ring-primary/30 focus:border-primary/50 focus:outline-none transition-all"
|
|
823
|
+
/>
|
|
824
|
+
<p className="text-xs text-text-muted mt-1">
|
|
825
|
+
Supported formats: protocol://user:pass@host:port, host:port:user:pass
|
|
826
|
+
</p>
|
|
827
|
+
</div>
|
|
828
|
+
|
|
829
|
+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
830
|
+
<Button fullWidth onClick={handleBatchImport} disabled={!batchImportText.trim() || importing}>
|
|
831
|
+
{importing ? "Importing..." : "Import"}
|
|
832
|
+
</Button>
|
|
833
|
+
<Button fullWidth variant="ghost" onClick={closeBatchImportModal} disabled={importing}>
|
|
834
|
+
Cancel
|
|
835
|
+
</Button>
|
|
836
|
+
</div>
|
|
837
|
+
</div>
|
|
838
|
+
</Modal>
|
|
839
|
+
|
|
840
|
+
<Modal
|
|
841
|
+
isOpen={showVercelModal}
|
|
842
|
+
title="Deploy Vercel Relay"
|
|
843
|
+
onClose={closeVercelModal}
|
|
844
|
+
>
|
|
845
|
+
<div className="flex flex-col gap-4">
|
|
846
|
+
<div className="rounded-lg bg-blue-500/5 border border-blue-500/10 p-3 flex flex-col gap-1.5">
|
|
847
|
+
<p className="text-sm text-text-main font-medium">What is Vercel Relay?</p>
|
|
848
|
+
<p className="text-xs text-text-muted">
|
|
849
|
+
Deploys an edge relay function to Vercel. All AI provider requests will be forwarded through Vercel's edge network, masking your real IP from providers.
|
|
850
|
+
</p>
|
|
851
|
+
<ul className="text-xs text-text-muted list-disc pl-4 space-y-0.5">
|
|
852
|
+
<li>Your IP is replaced by Vercel's dynamic edge IPs (hundreds of IPs across 20+ global regions)</li>
|
|
853
|
+
<li>Vercel serves millions of apps — providers can't block Vercel IPs without affecting legitimate traffic</li>
|
|
854
|
+
<li>Free tier: 100GB bandwidth/month, 500K edge invocations</li>
|
|
855
|
+
<li>Deploy multiple relays on different accounts for more IP diversity</li>
|
|
856
|
+
</ul>
|
|
857
|
+
</div>
|
|
858
|
+
<Input
|
|
859
|
+
label="Vercel API Token"
|
|
860
|
+
value={vercelForm.vercelToken}
|
|
861
|
+
onChange={(e) => setVercelForm((prev) => ({ ...prev, vercelToken: e.target.value }))}
|
|
862
|
+
placeholder="your-vercel-api-token"
|
|
863
|
+
hint={<>Token is used once for deployment and not stored. <a href="https://vercel.com/account/tokens" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Get token →</a></>}
|
|
864
|
+
type="password"
|
|
865
|
+
/>
|
|
866
|
+
<Input
|
|
867
|
+
label="Project Name"
|
|
868
|
+
value={vercelForm.projectName}
|
|
869
|
+
onChange={(e) => setVercelForm((prev) => ({ ...prev, projectName: e.target.value }))}
|
|
870
|
+
placeholder="my-relay"
|
|
871
|
+
hint="Unique name for your Vercel project. Leave empty for auto-generated name."
|
|
872
|
+
/>
|
|
873
|
+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
874
|
+
<Button
|
|
875
|
+
fullWidth
|
|
876
|
+
onClick={handleVercelDeploy}
|
|
877
|
+
disabled={!vercelForm.vercelToken.trim() || deploying}
|
|
878
|
+
>
|
|
879
|
+
{deploying ? "Deploying... (may take ~1 min)" : "Deploy"}
|
|
880
|
+
</Button>
|
|
881
|
+
<Button fullWidth variant="ghost" onClick={closeVercelModal} disabled={deploying}>
|
|
882
|
+
Cancel
|
|
883
|
+
</Button>
|
|
884
|
+
</div>
|
|
885
|
+
</div>
|
|
886
|
+
</Modal>
|
|
887
|
+
|
|
888
|
+
<Modal
|
|
889
|
+
isOpen={showCloudflareModal}
|
|
890
|
+
title="Deploy Cloudflare Relay"
|
|
891
|
+
onClose={closeCloudflareModal}
|
|
892
|
+
>
|
|
893
|
+
<div className="flex flex-col gap-4">
|
|
894
|
+
<div className="rounded-lg bg-orange-500/5 border border-orange-500/10 p-3 flex flex-col gap-1.5">
|
|
895
|
+
<p className="text-sm text-text-main font-medium">What is Cloudflare Relay?</p>
|
|
896
|
+
<p className="text-xs text-text-muted">
|
|
897
|
+
Deploys a Cloudflare Worker as a proxy relay. All AI provider requests will be forwarded through Cloudflare's global edge network.
|
|
898
|
+
</p>
|
|
899
|
+
<ul className="text-xs text-text-muted list-disc pl-4 space-y-0.5">
|
|
900
|
+
<li>High performance global routing and IP masking via Cloudflare Workers</li>
|
|
901
|
+
<li>Free tier: 100,000 requests per day</li>
|
|
902
|
+
<li>Requires Cloudflare Account ID and a Workers API Token (Edit Workers permission)</li>
|
|
903
|
+
</ul>
|
|
904
|
+
<div className="mt-2 pt-2 border-t border-orange-500/10 text-xs text-text-muted">
|
|
905
|
+
<p className="font-medium text-text-main mb-1">How to generate your API Token:</p>
|
|
906
|
+
<ol className="list-decimal pl-4 space-y-0.5">
|
|
907
|
+
<li>Go to <b>My Profile</b> → <b>API Tokens</b> → <b>Create Token</b></li>
|
|
908
|
+
<li>Scroll down to <b>Custom Token</b> and click <b>Get started</b></li>
|
|
909
|
+
<li>Under <b>Permissions</b>: Account | Workers Scripts | Edit</li>
|
|
910
|
+
<li>Under <b>Account Resources</b>: Include | Account | <i>Your Account Name</i></li>
|
|
911
|
+
<li>Click <b>Continue to summary</b> → <b>Create Token</b></li>
|
|
912
|
+
</ol>
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
<Input
|
|
916
|
+
label="Account ID"
|
|
917
|
+
value={cloudflareForm.accountId}
|
|
918
|
+
onChange={(e) => setCloudflareForm((prev) => ({ ...prev, accountId: e.target.value }))}
|
|
919
|
+
placeholder="your-cloudflare-account-id"
|
|
920
|
+
hint={<>Found on the right side of the Cloudflare dashboard overview page.</>}
|
|
921
|
+
/>
|
|
922
|
+
<Input
|
|
923
|
+
label="API Token"
|
|
924
|
+
value={cloudflareForm.apiToken}
|
|
925
|
+
onChange={(e) => setCloudflareForm((prev) => ({ ...prev, apiToken: e.target.value }))}
|
|
926
|
+
placeholder="your-cloudflare-api-token"
|
|
927
|
+
hint={<>Requires "Workers Scripts: Edit" permission. <a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Get token →</a></>}
|
|
928
|
+
type="password"
|
|
929
|
+
/>
|
|
930
|
+
<Input
|
|
931
|
+
label="Worker Name"
|
|
932
|
+
value={cloudflareForm.projectName}
|
|
933
|
+
onChange={(e) => setCloudflareForm((prev) => ({ ...prev, projectName: e.target.value }))}
|
|
934
|
+
placeholder="my-relay"
|
|
935
|
+
hint="Unique name for your Cloudflare Worker. Leave empty for auto-generated name."
|
|
936
|
+
/>
|
|
937
|
+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
938
|
+
<Button
|
|
939
|
+
fullWidth
|
|
940
|
+
onClick={handleCloudflareDeploy}
|
|
941
|
+
disabled={!cloudflareForm.accountId.trim() || !cloudflareForm.apiToken.trim() || deploying}
|
|
942
|
+
>
|
|
943
|
+
{deploying ? "Deploying..." : "Deploy Worker"}
|
|
944
|
+
</Button>
|
|
945
|
+
<Button fullWidth variant="ghost" onClick={closeCloudflareModal} disabled={deploying}>
|
|
946
|
+
Cancel
|
|
947
|
+
</Button>
|
|
948
|
+
</div>
|
|
949
|
+
</div>
|
|
950
|
+
</Modal>
|
|
951
|
+
|
|
952
|
+
<Modal
|
|
953
|
+
isOpen={showDenoModal}
|
|
954
|
+
title="Deploy Deno Relay"
|
|
955
|
+
onClose={closeDenoModal}
|
|
956
|
+
>
|
|
957
|
+
<div className="flex flex-col gap-4">
|
|
958
|
+
<div className="rounded-lg bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 p-3 flex flex-col gap-1.5">
|
|
959
|
+
<p className="text-sm text-text-main font-medium">What is Deno Relay?</p>
|
|
960
|
+
<p className="text-xs text-text-muted">
|
|
961
|
+
Deploys a relay worker to Deno Deploy's global edge network. All AI provider requests are forwarded through Deno's edge, masking your real IP.
|
|
962
|
+
</p>
|
|
963
|
+
<ul className="text-xs text-text-muted list-disc pl-4 space-y-0.5">
|
|
964
|
+
<li>Deno Deploy v2 runs on a high-performance global edge network</li>
|
|
965
|
+
<li>Free tier: 1M requests & 100GiB outbound traffic per month</li>
|
|
966
|
+
<li>No per-request CPU time limits (unlike Vercel/Cloudflare)</li>
|
|
967
|
+
<li>Support up to 20 active apps & 50 custom domains</li>
|
|
968
|
+
<li>Deploy multiple relays for maximum IP diversity</li>
|
|
969
|
+
</ul>
|
|
970
|
+
<div className="mt-2 pt-2 border-t border-black/10 dark:border-white/10 text-xs text-text-muted">
|
|
971
|
+
<p className="font-medium text-text-main mb-1">How to generate API token:</p>
|
|
972
|
+
<ol className="list-decimal pl-4 space-y-0.5">
|
|
973
|
+
<li>Go to <b>console.deno.com</b></li>
|
|
974
|
+
<li>Select your <b>Organization</b> → <b>Settings</b> → <b>Organization Tokens</b></li>
|
|
975
|
+
<li>Create a <b>Organization Token</b> (prefix <b>ddo_</b>)</li>
|
|
976
|
+
</ol>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
<Input
|
|
980
|
+
label="Deno Deploy API Token"
|
|
981
|
+
value={denoForm.denoToken}
|
|
982
|
+
onChange={(e) => setDenoForm((prev) => ({ ...prev, denoToken: e.target.value }))}
|
|
983
|
+
placeholder="ddo_xxxxxxxxxxxxxxxx"
|
|
984
|
+
hint={<>Token is used once for deployment, not stored. Found in Organization Settings.</>}
|
|
985
|
+
type="password"
|
|
986
|
+
/>
|
|
987
|
+
<Input
|
|
988
|
+
label="Organization Domain"
|
|
989
|
+
value={denoForm.orgDomain}
|
|
990
|
+
onChange={(e) => setDenoForm((prev) => ({ ...prev, orgDomain: e.target.value }))}
|
|
991
|
+
placeholder="your-org.deno.net"
|
|
992
|
+
hint="Organization's default domain. Your relay URL will be in the format: https://my-relay.your-org.deno.net"
|
|
993
|
+
/>
|
|
994
|
+
<Input
|
|
995
|
+
label="App Name"
|
|
996
|
+
value={denoForm.projectName}
|
|
997
|
+
onChange={(e) => setDenoForm((prev) => ({ ...prev, projectName: e.target.value }))}
|
|
998
|
+
placeholder="deno-relay"
|
|
999
|
+
hint="Unique app name. Leave empty for auto-generated name."
|
|
1000
|
+
/>
|
|
1001
|
+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
1002
|
+
<Button
|
|
1003
|
+
fullWidth
|
|
1004
|
+
onClick={handleDenoDeploy}
|
|
1005
|
+
disabled={!denoForm.denoToken.trim() || !denoForm.orgDomain.trim() || deploying}
|
|
1006
|
+
>
|
|
1007
|
+
{deploying ? "Deploying..." : "Deploy Relay"}
|
|
1008
|
+
</Button>
|
|
1009
|
+
<Button fullWidth variant="ghost" onClick={closeDenoModal} disabled={deploying}>
|
|
1010
|
+
Cancel
|
|
1011
|
+
</Button>
|
|
1012
|
+
</div>
|
|
1013
|
+
</div>
|
|
1014
|
+
</Modal>
|
|
1015
|
+
|
|
1016
|
+
<Modal
|
|
1017
|
+
isOpen={showFormModal}
|
|
1018
|
+
title={editingProxyPool ? "Edit Proxy Pool" : "Add Proxy Pool"}
|
|
1019
|
+
onClose={closeFormModal}
|
|
1020
|
+
>
|
|
1021
|
+
<div className="flex flex-col gap-4">
|
|
1022
|
+
<Input
|
|
1023
|
+
label="Name"
|
|
1024
|
+
value={formData.name}
|
|
1025
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
|
|
1026
|
+
placeholder="Office Proxy"
|
|
1027
|
+
/>
|
|
1028
|
+
<Input
|
|
1029
|
+
label="Proxy URL"
|
|
1030
|
+
value={formData.proxyUrl}
|
|
1031
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
|
1032
|
+
placeholder="http://127.0.0.1:7897"
|
|
1033
|
+
/>
|
|
1034
|
+
<Input
|
|
1035
|
+
label="No Proxy"
|
|
1036
|
+
value={formData.noProxy}
|
|
1037
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, noProxy: e.target.value }))}
|
|
1038
|
+
placeholder="localhost,127.0.0.1,.internal"
|
|
1039
|
+
hint="Comma-separated hosts/domains to bypass proxy"
|
|
1040
|
+
/>
|
|
1041
|
+
|
|
1042
|
+
<div className="flex flex-col gap-3 rounded-lg border border-border/50 p-3 sm:flex-row sm:items-center sm:justify-between">
|
|
1043
|
+
<div>
|
|
1044
|
+
<p className="font-medium text-sm">Active</p>
|
|
1045
|
+
<p className="text-xs text-text-muted">Inactive pools are ignored by runtime resolution.</p>
|
|
1046
|
+
</div>
|
|
1047
|
+
<Toggle
|
|
1048
|
+
checked={formData.isActive === true}
|
|
1049
|
+
onChange={() => setFormData((prev) => ({ ...prev, isActive: !prev.isActive }))}
|
|
1050
|
+
disabled={saving}
|
|
1051
|
+
/>
|
|
1052
|
+
</div>
|
|
1053
|
+
|
|
1054
|
+
<div className="flex flex-col gap-3 rounded-lg border border-border/50 p-3 sm:flex-row sm:items-center sm:justify-between">
|
|
1055
|
+
<div>
|
|
1056
|
+
<p className="font-medium text-sm">Strict Proxy</p>
|
|
1057
|
+
<p className="text-xs text-text-muted">Fail request if proxy is unreachable instead of falling back to direct.</p>
|
|
1058
|
+
</div>
|
|
1059
|
+
<Toggle
|
|
1060
|
+
checked={formData.strictProxy === true}
|
|
1061
|
+
onChange={() => setFormData((prev) => ({ ...prev, strictProxy: !prev.strictProxy }))}
|
|
1062
|
+
disabled={saving}
|
|
1063
|
+
/>
|
|
1064
|
+
</div>
|
|
1065
|
+
|
|
1066
|
+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
1067
|
+
<Button
|
|
1068
|
+
fullWidth
|
|
1069
|
+
onClick={handleSave}
|
|
1070
|
+
disabled={!formData.name.trim() || !formData.proxyUrl.trim() || saving}
|
|
1071
|
+
>
|
|
1072
|
+
{saving ? "Saving..." : "Save"}
|
|
1073
|
+
</Button>
|
|
1074
|
+
<Button fullWidth variant="ghost" onClick={closeFormModal} disabled={saving}>
|
|
1075
|
+
Cancel
|
|
1076
|
+
</Button>
|
|
1077
|
+
</div>
|
|
1078
|
+
</div>
|
|
1079
|
+
</Modal>
|
|
1080
|
+
|
|
1081
|
+
{/* Confirm Modal */}
|
|
1082
|
+
<ConfirmModal
|
|
1083
|
+
isOpen={!!confirmState}
|
|
1084
|
+
onClose={() => setConfirmState(null)}
|
|
1085
|
+
onConfirm={confirmState?.onConfirm}
|
|
1086
|
+
title={confirmState?.title || "Confirm"}
|
|
1087
|
+
message={confirmState?.message}
|
|
1088
|
+
variant="danger"
|
|
1089
|
+
/>
|
|
1090
|
+
</div>
|
|
1091
|
+
);
|
|
1092
|
+
}
|