mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-14 16:02:53 +09:00
Add a Storage tab listing and managing Cloudflare's account-scoped storage primitives — R2 buckets, KV namespaces, and D1 databases. Management only (list/create/delete); the object/key/row data planes are out of scope. - CloudflareClient: r2/buckets, storage/kv/namespaces, d1/database list+create +delete. - CloudflareService: combined list_storage (each product fetched independently so a missing token scope degrades to a per-product error, not a dead tab), validated create/delete for each. R2 bucket names validated S3-style. - /api/v1/cloudflare/zones/:id/storage* routes (read any user; writes admin). - StoragePanel (three generic create/list/delete sections; R2 notes its S3-compatibility for future backup targeting); api methods; SCSS. No new tables — wiring an R2 bucket as an S3 backup backend (minting scoped R2 keys) is a deliberate follow-up, noted in the UI. Tests: aggregate listing + per-product error degradation, R2 name validation, create/delete, KV/D1 required-field guards (6). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
181 lines
5.8 KiB
JavaScript
181 lines
5.8 KiB
JavaScript
// Cloudflare operations — zone settings (SSL/TLS, Speed, Caching, Security) and
|
|
// one-click hardening, layered on the existing Cloudflare DNS connection. Zones
|
|
// are addressed by their ServerKit DNS zone id (same as the /dns API).
|
|
|
|
export async function getCloudflareZoneSettings(zoneId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/settings`);
|
|
}
|
|
|
|
export async function getCloudflareZoneSetting(zoneId, settingId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/settings/${settingId}`);
|
|
}
|
|
|
|
export async function updateCloudflareZoneSetting(zoneId, settingId, value) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/settings/${settingId}`, {
|
|
method: 'PATCH',
|
|
body: { value },
|
|
});
|
|
}
|
|
|
|
export async function applyCloudflareSettingsPreset(zoneId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/settings/apply-preset`, {
|
|
method: 'POST',
|
|
});
|
|
}
|
|
|
|
// Purge the zone's Cloudflare cache. payload is { purge_everything: true } or
|
|
// { files: [...] } (and on Enterprise, hosts/prefixes/tags).
|
|
export async function purgeCloudflareCache(zoneId, payload) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/purge-cache`, {
|
|
method: 'POST',
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
// WAF custom firewall rules (http_request_firewall_custom phase).
|
|
export async function getCloudflareWafRules(zoneId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/waf/rules`);
|
|
}
|
|
|
|
export async function addCloudflareWafRule(zoneId, rule) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/waf/rules`, {
|
|
method: 'POST',
|
|
body: rule,
|
|
});
|
|
}
|
|
|
|
export async function applyCloudflareWafPreset(zoneId, presetKey, params = {}) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/waf/presets/${presetKey}`, {
|
|
method: 'POST',
|
|
body: { params },
|
|
});
|
|
}
|
|
|
|
export async function updateCloudflareWafRule(zoneId, rulesetId, ruleId, fields) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/waf/rulesets/${rulesetId}/rules/${ruleId}`, {
|
|
method: 'PATCH',
|
|
body: fields,
|
|
});
|
|
}
|
|
|
|
export async function deleteCloudflareWafRule(zoneId, rulesetId, ruleId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/waf/rulesets/${rulesetId}/rules/${ruleId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
// Workers (edge hosting). Account is resolved from the zone server-side.
|
|
export async function getCloudflareWorkers(zoneId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/workers`);
|
|
}
|
|
|
|
export async function deployCloudflareWorker(zoneId, payload) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/workers`, {
|
|
method: 'POST',
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
export async function deleteCloudflareWorker(zoneId, name) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/workers/${encodeURIComponent(name)}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
export async function addCloudflareWorkerRoute(zoneId, pattern, script) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/workers/routes`, {
|
|
method: 'POST',
|
|
body: { pattern, script },
|
|
});
|
|
}
|
|
|
|
export async function deleteCloudflareWorkerRoute(zoneId, routeId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/workers/routes/${routeId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
// Cloudflare Tunnels (cloudflared) — expose a local service through the edge.
|
|
export async function getCloudflareTunnels(zoneId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/tunnels`);
|
|
}
|
|
|
|
export async function createCloudflareTunnel(zoneId, name) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/tunnels`, {
|
|
method: 'POST',
|
|
body: { name },
|
|
});
|
|
}
|
|
|
|
export async function deleteCloudflareTunnel(zoneId, tunnelId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/tunnels/${tunnelId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
export async function getCloudflareTunnelInstall(zoneId, tunnelId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/tunnels/${tunnelId}/install`);
|
|
}
|
|
|
|
export async function getCloudflareTunnelHostnames(zoneId, tunnelId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/tunnels/${tunnelId}/hostnames`);
|
|
}
|
|
|
|
export async function addCloudflareTunnelHostname(zoneId, tunnelId, hostname, service) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/tunnels/${tunnelId}/hostnames`, {
|
|
method: 'POST',
|
|
body: { hostname, service },
|
|
});
|
|
}
|
|
|
|
export async function removeCloudflareTunnelHostname(zoneId, tunnelId, hostname) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/tunnels/${tunnelId}/hostnames`, {
|
|
method: 'DELETE',
|
|
body: { hostname },
|
|
});
|
|
}
|
|
|
|
// Developer platform — R2 buckets, KV namespaces, D1 databases (account-scoped).
|
|
export async function getCloudflareStorage(zoneId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/storage`);
|
|
}
|
|
|
|
export async function createCloudflareR2Bucket(zoneId, name) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/storage/r2`, {
|
|
method: 'POST',
|
|
body: { name },
|
|
});
|
|
}
|
|
|
|
export async function deleteCloudflareR2Bucket(zoneId, name) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/storage/r2/${encodeURIComponent(name)}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
export async function createCloudflareKvNamespace(zoneId, title) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/storage/kv`, {
|
|
method: 'POST',
|
|
body: { title },
|
|
});
|
|
}
|
|
|
|
export async function deleteCloudflareKvNamespace(zoneId, namespaceId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/storage/kv/${namespaceId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
export async function createCloudflareD1Database(zoneId, name) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/storage/d1`, {
|
|
method: 'POST',
|
|
body: { name },
|
|
});
|
|
}
|
|
|
|
export async function deleteCloudflareD1Database(zoneId, databaseId) {
|
|
return this.request(`/cloudflare/zones/${zoneId}/storage/d1/${databaseId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|