Преглед изворни кода

dev: automated commit - 2026-03-08 15:27:57

Mariano Z. пре 2 недеља
родитељ
комит
b5c7e06b99

+ 2 - 1
frontend/src/api/client.ts

@@ -21,5 +21,6 @@ export const api = {
     request<T>(path, { method: 'PUT', body: JSON.stringify(body) }),
   patch: <T>(path: string, body?: unknown) =>
     request<T>(path, { method: 'PATCH', body: body ? JSON.stringify(body) : undefined }),
-  delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
+  delete: <T>(path: string, params?: Record<string, string>) =>
+    request<T>(params ? `${path}?${new URLSearchParams(params)}` : path, { method: 'DELETE' }),
 }

+ 25 - 3
frontend/src/components/DeleteConfirmDialog.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import { ref } from 'vue'
 import {
   AlertDialog,
   AlertDialogAction,
@@ -9,17 +10,32 @@ import {
   AlertDialogHeader,
   AlertDialogTitle,
 } from '@/components/ui/alert-dialog'
+import { Switch } from '@/components/ui/switch'
+import { Label } from '@/components/ui/label'
 
 defineProps<{
   open: boolean
   title?: string
   description?: string
+  showCloudflareToggle?: boolean
 }>()
 
 const emit = defineEmits<{
   (e: 'update:open', value: boolean): void
-  (e: 'confirm'): void
+  (e: 'confirm', deleteFromCloudflare: boolean): void
 }>()
+
+const deleteFromCloudflare = ref(false)
+
+function handleConfirm() {
+  emit('confirm', deleteFromCloudflare.value)
+  deleteFromCloudflare.value = false
+}
+
+function handleCancel() {
+  emit('update:open', false)
+  deleteFromCloudflare.value = false
+}
 </script>
 
 <template>
@@ -31,11 +47,17 @@ const emit = defineEmits<{
           {{ description ?? 'This action cannot be undone.' }}
         </AlertDialogDescription>
       </AlertDialogHeader>
+      <div v-if="showCloudflareToggle" class="flex items-center gap-3 py-2">
+        <Switch id="cf-delete-toggle" v-model:checked="deleteFromCloudflare" />
+        <Label for="cf-delete-toggle" class="cursor-pointer">
+          Also delete from Cloudflare
+        </Label>
+      </div>
       <AlertDialogFooter>
-        <AlertDialogCancel @click="emit('update:open', false)">Cancel</AlertDialogCancel>
+        <AlertDialogCancel @click="handleCancel">Cancel</AlertDialogCancel>
         <AlertDialogAction
           class="bg-destructive text-white hover:bg-destructive/90"
-          @click="emit('confirm')"
+          @click="handleConfirm"
         >
           Delete
         </AlertDialogAction>

+ 6 - 2
frontend/src/views/RecordsView.vue

@@ -109,10 +109,13 @@ async function toggleStatic(record: DnsRecord) {
   }
 }
 
-async function confirmDelete() {
+async function confirmDelete(deleteFromCloudflare: boolean) {
   if (!deletingRecord.value) return
   try {
-    await api.delete(`/api/records/${deletingRecord.value.id}`)
+    await api.delete(
+      `/api/records/${deletingRecord.value.id}`,
+      deleteFromCloudflare ? { cloudflare: 'true' } : undefined,
+    )
     toast.success('Record deleted')
     deleteOpen.value = false
     await loadData()
@@ -493,6 +496,7 @@ onMounted(loadData)
       v-model:open="deleteOpen"
       title="Delete record?"
       :description="`This will permanently delete the record '${deletingRecord?.name}'.`"
+      :show-cloudflare-toggle="true"
       @confirm="confirmDelete"
     />
   </div>

+ 6 - 0
internal/cloudflare/client.go

@@ -89,6 +89,12 @@ func CreateDNSRecord(ctx context.Context, apiKey, zoneID string, rec DNSRecord)
 	return created, nil
 }
 
+func DeleteDNSRecord(ctx context.Context, apiKey, zoneID, recordID string) error {
+	url := fmt.Sprintf("%s/zones/%s/dns_records/%s", baseURL, zoneID, recordID)
+	_, err := doRequest(ctx, http.MethodDelete, url, apiKey, nil)
+	return err
+}
+
 func UpdateDNSRecord(ctx context.Context, apiKey, zoneID, recordID string, rec DNSRecord) error {
 	url := fmt.Sprintf("%s/zones/%s/dns_records/%s", baseURL, zoneID, recordID)
 

+ 15 - 0
internal/handler/record.go

@@ -195,6 +195,21 @@ func (h *RecordHandler) Delete(c echo.Context) error {
 		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"})
 	}
 
+	if c.QueryParam("cloudflare") == "true" {
+		record, err := h.q.GetRecord(c.Request().Context(), id)
+		if err != nil {
+			return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to fetch record"})
+		}
+		zone, err := h.q.GetZone(c.Request().Context(), record.ZoneID)
+		if err != nil {
+			return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to fetch zone"})
+		}
+		if err := cf.DeleteDNSRecord(c.Request().Context(), zone.ApiKey, zone.ZoneID, record.CfRecordID); err != nil {
+			slog.Error("failed to delete record on Cloudflare", "record_id", record.ID, "error", err)
+			return c.JSON(http.StatusBadGateway, map[string]string{"error": "failed to delete record on Cloudflare: " + err.Error()})
+		}
+	}
+
 	if err := h.q.DeleteRecord(c.Request().Context(), id); err != nil {
 		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to delete record"})
 	}