From 28eea2a7aee165391e0f0e985f7e9f2d237cebbc Mon Sep 17 00:00:00 2001 From: ale Date: Sun, 26 Oct 2025 12:31:35 +0100 Subject: [PATCH] delete bulk and export/import Signed-off-by: ale --- app/api/domains/[domain]/bulk-delete/route.js | 26 ++ app/api/domains/[domain]/export/route.js | 27 +++ app/api/domains/[domain]/import/route.js | 37 +++ components/DNSManager.js | 229 +++++++++++++++++- lib/ovh-service.js | 226 +++++++++++++++++ 5 files changed, 536 insertions(+), 9 deletions(-) create mode 100644 app/api/domains/[domain]/bulk-delete/route.js create mode 100644 app/api/domains/[domain]/export/route.js create mode 100644 app/api/domains/[domain]/import/route.js diff --git a/app/api/domains/[domain]/bulk-delete/route.js b/app/api/domains/[domain]/bulk-delete/route.js new file mode 100644 index 0000000..ffcccb0 --- /dev/null +++ b/app/api/domains/[domain]/bulk-delete/route.js @@ -0,0 +1,26 @@ +import { NextResponse } from 'next/server'; +import ovhService from '@/lib/ovh-service'; + +export async function POST(request, { params }) { + try { + const { domain } = await params; + const { recordIds } = await request.json(); + + if (!recordIds || recordIds.length === 0) { + return NextResponse.json( + { success: false, error: 'Record IDs are required' }, + { status: 400 } + ); + } + + const results = await ovhService.bulkDeleteRecords(domain, recordIds); + + return NextResponse.json({ success: true, results }); + } catch (error) { + console.error('Error bulk deleting DNS records:', error); + return NextResponse.json( + { success: false, error: error.message }, + { status: 500 } + ); + } +} diff --git a/app/api/domains/[domain]/export/route.js b/app/api/domains/[domain]/export/route.js new file mode 100644 index 0000000..851c4fb --- /dev/null +++ b/app/api/domains/[domain]/export/route.js @@ -0,0 +1,27 @@ +import { NextResponse } from 'next/server'; +import ovhService from '@/lib/ovh-service'; + +export async function GET(request, { params }) { + try { + const { domain } = await params; + + // Get all records + const records = await ovhService.getDNSRecords(domain); + + // Export to BIND9 format + const zoneFile = ovhService.exportToBind9(domain, records); + + return new NextResponse(zoneFile, { + headers: { + 'Content-Type': 'text/plain', + 'Content-Disposition': `attachment; filename="${domain}.zone"` + } + }); + } catch (error) { + console.error('Error exporting DNS zone:', error); + return NextResponse.json( + { success: false, error: error.message }, + { status: 500 } + ); + } +} diff --git a/app/api/domains/[domain]/import/route.js b/app/api/domains/[domain]/import/route.js new file mode 100644 index 0000000..10dcf4d --- /dev/null +++ b/app/api/domains/[domain]/import/route.js @@ -0,0 +1,37 @@ +import { NextResponse } from 'next/server'; +import ovhService from '@/lib/ovh-service'; + +export async function POST(request, { params }) { + try { + const { domain } = await params; + const { zoneContent, replaceAll = false } = await request.json(); + + if (!zoneContent) { + return NextResponse.json( + { success: false, error: 'Zone content is required' }, + { status: 400 } + ); + } + + const results = await ovhService.importFromBind9(domain, zoneContent, replaceAll); + + const successCount = results.filter(r => r.success).length; + const failureCount = results.filter(r => !r.success).length; + + return NextResponse.json({ + success: true, + results, + summary: { + total: results.length, + success: successCount, + failed: failureCount + } + }); + } catch (error) { + console.error('Error importing DNS zone:', error); + return NextResponse.json( + { success: false, error: error.message }, + { status: 500 } + ); + } +} diff --git a/components/DNSManager.js b/components/DNSManager.js index f0f3549..da4b6fd 100644 --- a/components/DNSManager.js +++ b/components/DNSManager.js @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect } from 'react'; -import { Globe, Plus, Edit, Trash2, RefreshCw, Check, X, Search, Filter } from 'lucide-react'; +import { Globe, Plus, Edit, Trash2, RefreshCw, Check, X, Search, Filter, Download, Upload, Trash } from 'lucide-react'; const DNSManager = () => { const [domains, setDomains] = useState([]); @@ -25,6 +25,10 @@ const DNSManager = () => { target: '', type: 'A' }); + const [showImport, setShowImport] = useState(false); + const [importContent, setImportContent] = useState(''); + const [replaceAll, setReplaceAll] = useState(false); + const [importResult, setImportResult] = useState(null); const fetchDomains = async () => { try { @@ -168,6 +172,97 @@ const DNSManager = () => { } }; + const handleBulkDelete = async () => { + if (selectedRecords.size === 0) return; + + if (!confirm(`¿Está seguro de que desea eliminar ${selectedRecords.size} registro(s)?`)) { + return; + } + + try { + const response = await fetch(`/api/domains/${selectedDomain}/bulk-delete`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + recordIds: Array.from(selectedRecords) + }) + }); + + const data = await response.json(); + if (data.success) { + setSelectedRecords(new Set()); + fetchRecords(); + } + } catch (error) { + console.error('Error bulk deleting records:', error); + } + }; + + const handleExport = async () => { + if (!selectedDomain) return; + + try { + const response = await fetch(`/api/domains/${selectedDomain}/export`); + + if (response.ok) { + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${selectedDomain}.zone`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } + } catch (error) { + console.error('Error exporting zone:', error); + } + }; + + const handleImport = async () => { + if (!importContent.trim()) { + alert('Por favor, ingrese el contenido de la zona BIND9'); + return; + } + + try { + const response = await fetch(`/api/domains/${selectedDomain}/import`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + zoneContent: importContent, + replaceAll: replaceAll + }) + }); + + const data = await response.json(); + if (data.success) { + setImportResult(data); + setTimeout(() => { + setShowImport(false); + setImportContent(''); + setReplaceAll(false); + setImportResult(null); + fetchRecords(); + }, 3000); + } + } catch (error) { + console.error('Error importing zone:', error); + } + }; + + const handleFileImport = (e) => { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + setImportContent(event.target?.result || ''); + }; + reader.readAsText(file); + } + }; + const toggleRecordSelection = (recordId) => { const newSelection = new Set(selectedRecords); if (newSelection.has(recordId)) { @@ -271,12 +366,30 @@ const DNSManager = () => { ))} -
+
+ +
@@ -320,12 +433,22 @@ const DNSManager = () => { {selectedRecords.size} registro(s) seleccionado(s) - +
+ + +
)} @@ -605,6 +728,94 @@ const DNSManager = () => { )} + + {/* Import modal */} + {showImport && ( +
+
+

Importar Zona BIND9

+ + {importResult ? ( +
+

Importación completada

+

+ Total: {importResult.summary.total} | + Exitosos: {importResult.summary.success} | + Fallidos: {importResult.summary.failed} +

+
+ ) : ( + <> +
+ + +
+ +
+ +