New button to import a zone from bind file

This commit is contained in:
nemunaire 2023-02-28 01:50:53 +01:00
parent 625b20fa6d
commit 7940a42d0f
5 changed files with 164 additions and 2 deletions

View File

@ -55,6 +55,7 @@ func declareZonesRoutes(cfg *config.Options, router *gin.RouterGroup) {
apiZonesRoutes := router.Group("/zone/:zoneid")
apiZonesRoutes.Use(ZoneHandler)
apiZonesRoutes.POST("/import", importZone)
apiZonesRoutes.POST("/view", viewZone)
apiZonesRoutes.POST("/apply_changes", applyZone)
@ -246,6 +247,44 @@ func retrieveZone(c *gin.Context) {
c.JSON(http.StatusOK, &myZone.ZoneMeta)
}
// importZone takes a bind style file
func importZone(c *gin.Context) {
domain := c.MustGet("domain").(*happydns.Domain)
zone := c.MustGet("zone").(*happydns.Zone)
fd, _, err := c.Request.FormFile("zone")
if err != nil {
log.Printf("Error when retrieving zone file from %s: %s", c.ClientIP(), err.Error())
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to read your zone file: something is wrong in your request"})
return
}
defer fd.Close()
zp := dns.NewZoneParser(fd, domain.DomainName, "")
var rrs []dns.RR
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
rrs = append(rrs, rr)
}
zone.Services, _, err = svcs.AnalyzeZone(domain.DomainName, rrs)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
zone.LastModified = time.Now()
err = storage.MainStore.UpdateZone(zone)
if err != nil {
log.Printf("%s: Unable to UpdateZone in updateZoneService: %s", c.ClientIP(), err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Sorry, we are currently unable to update your zone. Please retry later."})
return
}
c.JSON(http.StatusOK, zone)
}
func diffZones(c *gin.Context) {
user := c.MustGet("LoggedUser").(*happydns.User)
domain := c.MustGet("domain").(*happydns.Domain)

View File

@ -40,6 +40,21 @@ export async function applyZone(domain: Domain | DomainInList, id: string, selec
return await handleApiResponse<ZoneMeta>(res);
}
export async function importZone(domain: Domain | DomainInList, id: string, file: any): Promise<ZoneMeta> {
const dnid = encodeURIComponent(domain.id);
id = encodeURIComponent(id);
const formData = new FormData();
formData.append("zone", file);
const res = await fetch(`/api/domains/${dnid}/zone/${id}/import`, {
method: 'POST',
headers: {'Accept': 'application/json'},
body: formData,
});
return await handleApiResponse<ZoneMeta>(res);
}
export async function diffZone(domain: Domain | DomainInList, id1: string, id2: string): Promise<Array<string>> {
const dnid = encodeURIComponent(domain.id);
id1 = encodeURIComponent(id1);

View File

@ -65,7 +65,8 @@
"reimport": "Re-import",
"view": "View my zone",
"propagate": "Publish my changes",
"rollback": "Roll back to this version"
"rollback": "Roll back to this version",
"upload": "Import a zone file"
},
"alert": {
"remove": "This action only removes {{domain}} from your happyDomain managed domains. All history and abstracted zones will be discarded. The domain {{domain}} remains fully intact at the provider. Are you sure you want to continue?",
@ -319,5 +320,10 @@
"resolver-description": "This is the server we will ask for the information.",
"ttl": "Remaining time in cache",
"showDNSSEC": "Show DNSSEC records in answer (if any)"
},
"zones": {
"upload": "Import a zone",
"import-text": "Import from text",
"import-file": "Import from file"
}
}

View File

@ -65,7 +65,8 @@
"view": "Voir ma zone",
"propagate": "Diffuser mes changements",
"rollback": "Revenir à cette version",
"do-migration": "Migrer maintenant"
"do-migration": "Migrer maintenant",
"upload": "Importer un fichier de zone"
},
"alert": {
"remove": "Cette action retirera définitivement le domaine {{domain}} de votre liste. L'historique et les zones abstraites seront supprimés. Cela ne supprimera ni n'affectera votre domaine auprès de votre fournisseur, ni ne modifiera ce qui est actuellement diffusé. Cela ne concerne seulement que ce que vous voyez dans happyDomain. Êtes-vous certain de vouloir continuer?",
@ -319,5 +320,10 @@
"new": "Nouveau groupe",
"no-group": "Divers",
"title": "Vos groupes"
},
"zones": {
"upload": "Importer une zone",
"import-text": "Depuis du texte",
"import-file": "Depuis un fichier de zone"
}
}

View File

@ -16,6 +16,8 @@
ModalHeader,
Row,
Spinner,
TabContent,
TabPane,
} from 'sveltestrap';
import {
@ -25,6 +27,7 @@
import {
applyZone as APIApplyZone,
diffZone as APIDiffZone,
importZone as APIImportZone,
retrieveZone as APIRetrieveZone,
viewZone as APIViewZone,
} from '$lib/api/zone';
@ -85,7 +88,9 @@
}
function retrieveZoneDone(zm: ZoneMeta): void {
uploadModalIsOpen = false;
retrievalInProgress = false;
uploadInProgress = false;
refreshDomains();
selectedHistory = zm.id;
}
@ -114,6 +119,35 @@
}
}
let uploadModalIsOpen = false;
let uploadInProgress = false;
let zoneImportContent = "";
let zoneImportFiles: FileList;
let uploadModalActiveTab: string|number = 0;
$: if (uploadModalIsOpen) {
uploadInProgress = false;
zoneImportContent = "";
uploadModalActiveTab = 0;
}
function importZone(): void {
if (domain && selectedHistory) {
uploadInProgress = true;
let file = new Blob([zoneImportContent], {"type": "text/plain"});
if (uploadModalActiveTab != "uploadText") {
file = zoneImportFiles[0];
}
APIImportZone(domain, selectedHistory, file).then(
retrieveZoneDone,
(err: any) => {
uploadInProgress = false;
throw err;
}
);
}
}
let viewZoneModalIsOpen = false;
let zoneContent: null | string = null;
function viewZone(): void {
@ -264,6 +298,20 @@
<div class="d-flex justify-content-between">
<label class="fw-bolder" for="zhistory">{$t('domains.history')}:</label>
<div class="d-flex gap-1">
<Button
outline
color="secondary"
size="sm"
title={$t('domains.actions.upload')}
disabled={uploadInProgress}
on:click={() => uploadModalIsOpen = true}
>
{#if uploadInProgress}
<Spinner size="sm" />
{:else}
<Icon name="cloud-upload" />
{/if}
</Button>
<Button
outline
color="secondary"
@ -390,6 +438,54 @@
</Row>
</Container>
<Modal
isOpen={uploadModalIsOpen}
size="lg"
>
<ModalHeader toggle={() => uploadModalIsOpen = false}>{$t('zones.upload')}</ModalHeader>
<ModalBody>
<TabContent on:tab={(e) => (uploadModalActiveTab = e.detail)}>
<TabPane tabId="uploadText" tab={$t('zones.import-text')} active>
<Input
class="mt-3"
type="textarea"
style="height: 200px;"
placeholder="@ 4269 IN SOA root ns 2042070136 ..."
bind:value={zoneImportContent}
/>
</TabPane>
<TabPane tabId="uploadFile" tab={$t('zones.import-file')}>
{#if uploadModalIsOpen}
<Input
class="mt-3"
type="file"
bind:files={zoneImportFiles}
/>
{/if}
</TabPane>
</TabContent>
</ModalBody>
<ModalFooter>
<Button
outline
color="secondary"
on:click={() => uploadModalIsOpen = false}
>
{$t('common.cancel')}
</Button>
<Button
color="primary"
disabled={uploadInProgress}
on:click={importZone}
>
{#if uploadInProgress}
<Spinner size="sm" />
{/if}
{$t('domains.actions.upload')}
</Button>
</ModalFooter>
</Modal>
<Modal
isOpen={deleteModalIsOpen}
size="lg"