feat: Add Cloudflare Workers proxy deployer and pool integration (#1360)
Co-authored-by: ansh <ansh@example.com>
This commit is contained in:
1 parent
315dcaa37a
commit
13a28452fb
6 files changed
+259
-7
No files matched your search
@@ -33,10 +33,12 @@ export default function ProxyPoolsPage() {
|
||||
const [showFormModal, setShowFormModal] = useState(false);
|
||||
const [showBatchImportModal, setShowBatchImportModal] = useState(false);
|
||||
const [showVercelModal, setShowVercelModal] = useState(false);
|
||||
const [showCloudflareModal, setShowCloudflareModal] = useState(false);
|
||||
const [editingProxyPool, setEditingProxyPool] = useState(null);
|
||||
const [formData, setFormData] = useState(normalizeFormData());
|
||||
const [batchImportText, setBatchImportText] = useState("");
|
||||
const [vercelForm, setVercelForm] = useState({ vercelToken: "", projectName: "vercel-relay" });
|
||||
const [cloudflareForm, setCloudflareForm] = useState({ accountId: "", apiToken: "", projectName: "cloudflare-relay" });
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [importing, setImporting] = useState(false);
|
||||
const [deploying, setDeploying] = useState(false);
|
||||
@@ -334,6 +336,16 @@ export default function ProxyPoolsPage() {
|
||||
setShowVercelModal(false);
|
||||
};
|
||||
|
||||
const openCloudflareModal = () => {
|
||||
setCloudflareForm({ accountId: "", apiToken: "", projectName: "cloudflare-relay" });
|
||||
setShowCloudflareModal(true);
|
||||
};
|
||||
|
||||
const closeCloudflareModal = () => {
|
||||
if (deploying) return;
|
||||
setShowCloudflareModal(false);
|
||||
};
|
||||
|
||||
const handleVercelDeploy = async () => {
|
||||
if (!vercelForm.vercelToken.trim()) return;
|
||||
setDeploying(true);
|
||||
@@ -359,6 +371,31 @@ export default function ProxyPoolsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloudflareDeploy = async () => {
|
||||
if (!cloudflareForm.accountId.trim() || !cloudflareForm.apiToken.trim()) return;
|
||||
setDeploying(true);
|
||||
try {
|
||||
const res = await fetch("/api/proxy-pools/cloudflare-deploy", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(cloudflareForm),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
await fetchProxyPools();
|
||||
closeCloudflareModal();
|
||||
notify.success(`Deployed: ${data.deployUrl}`);
|
||||
} else {
|
||||
notify.error(data.error || "Deploy failed");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error deploying Cloudflare relay:", error);
|
||||
notify.error("Deploy failed");
|
||||
} finally {
|
||||
setDeploying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const parseProxyLine = (line) => {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return null;
|
||||
@@ -495,6 +532,9 @@ export default function ProxyPoolsPage() {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 sm:flex sm:items-center">
|
||||
<Button size="sm" variant="secondary" icon="cloud" onClick={openCloudflareModal}>
|
||||
Cloudflare Relay
|
||||
</Button>
|
||||
<Button size="sm" variant="secondary" icon="cloud_upload" onClick={openVercelModal}>
|
||||
Vercel Relay
|
||||
</Button>
|
||||
@@ -588,6 +628,9 @@ export default function ProxyPoolsPage() {
|
||||
{pool.type === "vercel" && (
|
||||
<Badge variant="default" size="sm">vercel relay</Badge>
|
||||
)}
|
||||
{pool.type === "cloudflare" && (
|
||||
<Badge variant="default" size="sm">cloudflare relay</Badge>
|
||||
)}
|
||||
<Badge variant="default" size="sm">
|
||||
{pool.boundConnectionCount || 0} bound
|
||||
</Badge>
|
||||
@@ -722,6 +765,70 @@ export default function ProxyPoolsPage() {
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
isOpen={showCloudflareModal}
|
||||
title="Deploy Cloudflare Relay"
|
||||
onClose={closeCloudflareModal}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="rounded-lg bg-orange-500/5 border border-orange-500/10 p-3 flex flex-col gap-1.5">
|
||||
<p className="text-sm text-text-main font-medium">What is Cloudflare Relay?</p>
|
||||
<p className="text-xs text-text-muted">
|
||||
Deploys a Cloudflare Worker as a proxy relay. All AI provider requests will be forwarded through Cloudflare's global edge network.
|
||||
</p>
|
||||
<ul className="text-xs text-text-muted list-disc pl-4 space-y-0.5">
|
||||
<li>High performance global routing and IP masking via Cloudflare Workers</li>
|
||||
<li>Free tier: 100,000 requests per day</li>
|
||||
<li>Requires Cloudflare Account ID and a Workers API Token (Edit Workers permission)</li>
|
||||
</ul>
|
||||
<div className="mt-2 pt-2 border-t border-orange-500/10 text-xs text-text-muted">
|
||||
<p className="font-medium text-text-main mb-1">How to generate your API Token:</p>
|
||||
<ol className="list-decimal pl-4 space-y-0.5">
|
||||
<li>Go to <b>My Profile</b> → <b>API Tokens</b> → <b>Create Token</b></li>
|
||||
<li>Scroll down to <b>Custom Token</b> and click <b>Get started</b></li>
|
||||
<li>Under <b>Permissions</b>: Account | Workers Scripts | Edit</li>
|
||||
<li>Under <b>Account Resources</b>: Include | Account | <i>Your Account Name</i></li>
|
||||
<li>Click <b>Continue to summary</b> → <b>Create Token</b></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
label="Account ID"
|
||||
value={cloudflareForm.accountId}
|
||||
onChange={(e) => setCloudflareForm((prev) => ({ ...prev, accountId: e.target.value }))}
|
||||
placeholder="your-cloudflare-account-id"
|
||||
hint={<>Found on the right side of the Cloudflare dashboard overview page.</>}
|
||||
/>
|
||||
<Input
|
||||
label="API Token"
|
||||
value={cloudflareForm.apiToken}
|
||||
onChange={(e) => setCloudflareForm((prev) => ({ ...prev, apiToken: e.target.value }))}
|
||||
placeholder="your-cloudflare-api-token"
|
||||
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></>}
|
||||
type="password"
|
||||
/>
|
||||
<Input
|
||||
label="Worker Name"
|
||||
value={cloudflareForm.projectName}
|
||||
onChange={(e) => setCloudflareForm((prev) => ({ ...prev, projectName: e.target.value }))}
|
||||
placeholder="my-relay"
|
||||
hint="Unique name for your Cloudflare Worker. Leave empty for auto-generated name."
|
||||
/>
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
<Button
|
||||
fullWidth
|
||||
onClick={handleCloudflareDeploy}
|
||||
disabled={!cloudflareForm.accountId.trim() || !cloudflareForm.apiToken.trim() || deploying}
|
||||
>
|
||||
{deploying ? "Deploying..." : "Deploy Worker"}
|
||||
</Button>
|
||||
<Button fullWidth variant="ghost" onClick={closeCloudflareModal} disabled={deploying}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
isOpen={showFormModal}
|
||||
title={editingProxyPool ? "Edit Proxy Pool" : "Add Proxy Pool"}
|
||||
|
||||
Reference in new issue
Block a user