diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte index 8df2d14cf..91dbb91ec 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte @@ -59,7 +59,7 @@ name, value, ttl, - comment + comment || undefined ); break; @@ -69,7 +69,7 @@ name, value, ttl, - comment + comment || undefined ); break; @@ -80,7 +80,7 @@ value, ttl, priority, - comment + comment || undefined ); break; @@ -90,7 +90,7 @@ name, value, ttl, - comment + comment || undefined ); break; @@ -100,7 +100,7 @@ name, value, ttl, - comment + comment || undefined ); break; @@ -111,7 +111,7 @@ name, value, ttl, - comment + comment || undefined ); break; @@ -121,7 +121,7 @@ name, value, ttl, - comment + comment || undefined ); break; @@ -131,7 +131,7 @@ name, value, ttl, - comment + comment || undefined ); break; diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/importRecordModal.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/importRecordModal.svelte index fe61b49f8..b553e5a7b 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/importRecordModal.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/importRecordModal.svelte @@ -5,19 +5,247 @@ import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Icon, Layout, Tooltip, Typography, Upload } from '@appwrite.io/pink-svelte'; import { IconInfo } from '@appwrite.io/pink-icons-svelte'; + import { removeFile } from '$lib/helpers/files'; + import { sdk } from '$lib/stores/sdk'; + import type { DnsRecord } from '$lib/sdk/domains'; + import { page } from '$app/stores'; export let show = false; let files: FileList; let error = ''; + type ParsedRecords = { + A: Partial[]; + AAAA: Partial[]; + CNAME: Partial[]; + MX: Partial[]; + TXT: Partial[]; + NS: Partial[]; + SRV: Partial[]; + CAA: Partial[]; + PTR: Partial[]; + HTTPS: Partial[]; + ALIAS: Partial[]; + [key: string]: Partial[]; + }; + + function parseZoneFile(content: string): ParsedRecords { + const records: ParsedRecords = { + A: [], + AAAA: [], + CNAME: [], + MX: [], + TXT: [], + NS: [], + SRV: [], + CAA: [], + PTR: [], + HTTPS: [], + ALIAS: [] + }; + + // Split the content into lines and process each line + const lines = content.split('\n'); + + for (let line of lines) { + // Remove comments, but save them for record comment field + let comment = ''; + const commentIndex = line.indexOf(';'); + if (commentIndex !== -1) { + comment = line.substring(commentIndex + 1).trim(); + line = line.substring(0, commentIndex).trim(); + } + + // Skip empty lines or pure comment lines + if (!line.trim()) continue; + + // Split line into parts (whitespace separated) + const parts = line.trim().split(/\s+/); + + // Need at least a name, TTL/class/type, and value + if (parts.length < 3) continue; + + let recordType = ''; + let name = ''; + let value = ''; + let ttl = 3600; + let priority: number | undefined; + let weight: number | undefined; + let port: number | undefined; + + // Try to determine the record type from the parts + for (let i = 0; i < parts.length; i++) { + const part = parts[i].toUpperCase(); + if (['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SRV', 'CAA'].includes(part)) { + recordType = part; + name = parts[0] === '@' ? '' : parts[0]; // @ represents the domain itself + + // Handle TTL if it appears to be a number + const possibleTtl = parseInt(parts[i - 1]); + if (!isNaN(possibleTtl) && i > 0) { + ttl = possibleTtl; + } + + if (recordType === 'MX' && i + 2 < parts.length) { + priority = parseInt(parts[i + 1]); + value = parts[i + 2]; + } else if (recordType === 'SRV' && i + 4 < parts.length) { + priority = parseInt(parts[i + 1]); + weight = parseInt(parts[i + 2]); + port = parseInt(parts[i + 3]); + value = parts[i + 4]; + } else if (i + 1 < parts.length) { + value = parts.slice(i + 1).join(' '); + } + + break; + } + } + + if (recordType && value) { + if (recordType === 'TXT' && value.startsWith('"') && value.endsWith('"')) { + value = value.slice(1, -1); + } + + const record: Partial = { + name, + value, + ttl, + comment: comment || undefined + }; + + if (priority !== undefined) { + record.priority = priority; + } + + if (!records[recordType]) { + records[recordType] = []; + } + + records[recordType].push(record); + } + } + + return records; + } + async function handleSubmit() { try { - //TODO: create DNS records + if (!files?.length) return; + + const file = files[0]; + const content = await file.text(); + const parsedRecords = parseZoneFile(content); + + let recordCount = 0; + + for (const [type, records] of Object.entries(parsedRecords)) { + recordCount += records.length; + + for (const record of records) { + switch (type) { + case 'A': + await sdk.forConsole.domains.createRecordA( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.comment + ); + break; + case 'AAAA': + await sdk.forConsole.domains.createRecordAAAA( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.comment + ); + break; + case 'CNAME': + await sdk.forConsole.domains.createRecordCNAME( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.comment + ); + break; + case 'MX': + await sdk.forConsole.domains.createRecordMX( + $page.params.domain, + record.name, + record.value, + record.priority || 10, + record.ttl, + record.comment + ); + break; + case 'TXT': + await sdk.forConsole.domains.createRecordTXT( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.comment + ); + break; + case 'NS': + await sdk.forConsole.domains.createRecordNS( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.comment + ); + break; + case 'SRV': + await sdk.forConsole.domains.createRecordSRV( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.priority, + record.weight, + record.port, + record.comment + ); + break; + case 'CAA': + await sdk.forConsole.domains.createRecordCAA( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.comment + ); + break; + case 'PTR': + await sdk.forConsole.domains.createRecordHTTPS( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.comment + ); + break; + case 'ALIAS': + await sdk.forConsole.domains.createRecordAlias( + $page.params.domain, + record.name, + record.value, + record.ttl, + record.comment + ); + break; + } + } + } show = false; addNotification({ type: 'success', - message: `Presets have been added` + message: `${recordCount} DNS records have been imported successfully` }); trackEvent(Submit.RecordCreate); } catch (e) { @@ -25,6 +253,14 @@ trackError(e, Submit.RecordCreate); } } + + $: filesList = files?.length + ? Array.from(files).map((file) => { + let f = file as Partial & { removable: boolean }; + f.removable = true; + return f; + }) + : []; @@ -34,24 +270,41 @@ >. - - - - - - Drag and drop file here or click to upload - - - - + + + + Upload a .txt file with your DNS records + + + + + + + Drag and drop file here or click to upload + + + + + + Only .txt files allowed + - Only .txt files allowed - + Max file size 10MB + - Max file size 10MB - + - + {#if files?.length} + (files = removeFile(e.detail, files))} /> + {/if} +