Improve DNS editor

This commit is contained in:
Ralph J. Smit
2022-07-27 12:31:15 +02:00
parent 88b324b47a
commit 1ebcf75806
7 changed files with 268 additions and 156 deletions

View File

@@ -1,6 +1,6 @@
<template>
<Page>
<TopBar :breadcrumbs="breadcrumbs"/>
<TopBar :breadcrumbs="breadcrumbs" />
<Content>
<Container>
@@ -13,7 +13,7 @@
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs :site="site"/>
<Tabs :site="site" />
</template>
<template #segments>
<SettingsSegment>
@@ -23,8 +23,31 @@
</template>
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<FormInput :disabled="sending" :label="__('Name')" :errors="$page.props.errors.name" v-model="form.name"/>
<FormInput :disabled="sending" :label="__('IPv4 address')" :errors="$page.props.errors.content" v-model="form.content"/>
<FormInput :disabled="sending" :label="__('Name')" :errors="action === 'create' ? $page.props.errors.name : []" v-model="form.name" />
<FormInput :disabled="sending" :label="__('IPv4 address')" :errors="action === 'create' ? $page.props.errors.content : []" v-model="form.content" />
<div class="grid grid-cols-2 gap-x-4">
<FormSelect class="col-span-1" :disabled="sending" :label="__('Type')" :errors="action === 'create' ? $page.props.errors.type : []" v-model="form.type">
<option value="A">{{ __('A') }}</option>
<option value="AAAA">{{ __('AAAA') }}</option>
<option value="CNAME">{{ __('CNAME') }}</option>
<option value="HTTPS">{{ __('HTTPS') }}</option>
<option value="TXT">{{ __('TXT') }}</option>
<option value="SRV">{{ __('SRV') }}</option>
<option value="LOC">{{ __('LOC') }}</option>
<option value="MX">{{ __('MX') }}</option>
<option value="NS">{{ __('NS') }}</option>
<option value="CERT">{{ __('CERT') }}</option>
<option value="DNSKEY">{{ __('DNSKEY') }}</option>
<option value="DS">{{ __('DS') }}</option>
<option value="NAPTR">{{ __('NAPTR') }}</option>
<option value="SMIMEA">{{ __('SMIMEA') }}</option>
<option value="SSHFP">{{ __('SSHFP') }}</option>
<option value="SVCB">{{ __('SVCB') }}</option>
<option value="TLSA">{{ __('TLSA') }}</option>
</FormSelect>
<FormInput type="number" :disabled="sending" :label="__('TTL')" :errors="action === 'create' ? $page.props.errors.ttl : []" v-model="form.ttl" />
</div>
<FormActions>
<Button>{{ __('Save') }}</Button>
@@ -33,7 +56,7 @@
</template>
</SettingsSegment>
<EmptyImage v-if="!records.length && !loading" />
<EmptyImage v-if="records === null && !loading" />
<div v-if="loading" class="inline-flex px-4">
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
@@ -50,23 +73,50 @@
<Table caption="DNS records list overview">
<TableHead>
<TableRow>
<TableHeader>{{ __('Type') }}</TableHeader>
<TableHeader>{{ __('Name') }}</TableHeader>
<TableHeader>{{ __('Content') }}</TableHeader>
<TableHeader>{{ __('TTL') }}</TableHeader>
<TableHeader></TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow v-for="(record, index) in records" :key="record.id" class="px-2">
<TableData>
<Input :value="record.name" type="text" :id="index + 'x'" onkeyup="window.Vue.set(this.records, index, this.value)" />
<p v-text="records[index].name" />
<TableData class="pr-4">
<FormSelect :value="record.type" :disabled="sending" :id="record.id + '-type'" :errors="(action === 'update' && recordIdBeingUpdated === record.id) ? $page.props.errors.type : []">
<option value="A">{{ __('A') }}</option>
<option value="AAAA">{{ __('AAAA') }}</option>
<option value="CNAME">{{ __('CNAME') }}</option>
<option value="HTTPS">{{ __('HTTPS') }}</option>
<option value="TXT">{{ __('TXT') }}</option>
<option value="SRV">{{ __('SRV') }}</option>
<option value="LOC">{{ __('LOC') }}</option>
<option value="MX">{{ __('MX') }}</option>
<option value="NS">{{ __('NS') }}</option>
<option value="CERT">{{ __('CERT') }}</option>
<option value="DNSKEY">{{ __('DNSKEY') }}</option>
<option value="DS">{{ __('DS') }}</option>
<option value="NAPTR">{{ __('NAPTR') }}</option>
<option value="SMIMEA">{{ __('SMIMEA') }}</option>
<option value="SSHFP">{{ __('SSHFP') }}</option>
<option value="SVCB">{{ __('SVCB') }}</option>
<option value="TLSA">{{ __('TLSA') }}</option>
</FormSelect>
</TableData>
<TableData class="pr-4">
<FormInput class="col-span-2" :value="record.name" type="text" :id="record.id + '-name'" :errors="(action === 'update' && recordIdBeingUpdated === record.id) ? $page.props.errors.name : []" />
</TableData>
<TableData class="pr-4">
<FormInput class="col-span-2" :value="record.content" type="text" :id="record.id + '-content'" :errors="(action === 'update' && recordIdBeingUpdated === record.id) ? $page.props.errors.content : []" />
</TableData>
<TableData class="pr-4">
<FormInput class="!w-16" :value="record.ttl" type="number" :id="record.id + '-ttl'" :disabled="sending" :errors="(action === 'update' && recordIdBeingUpdated === record.id) ? $page.props.errors.ttl : []" />
</TableData>
<TableData>
<Input v-model="records[index].content" type="text" id="TODO" />
</TableData>
<TableData>
<Button @click="save(record, index)" variant="primary" size="sm">Save</Button>
<Button @click="confirmDelete(record)" variant="danger" size="sm">Delete</Button>
<div class="col-span-1 flex flex-col gap-y-2">
<Button @click="save(record, index)" variant="primary" size="sm">Save</Button>
<Button @click="confirmDelete(record)" variant="danger" size="sm">Delete</Button>
</div>
</TableData>
</TableRow>
</TableBody>
@@ -114,155 +164,184 @@ import TableBody from '@/components/TableBody'
import TableData from '@/components/TableData'
import EmptyImage from '@/components/EmptyImage'
import Input from "../../components/Input";
import FormSelect from "../../components/forms/FormSelect";
export default {
metaInfo() {
return {
title: `${this.__('DNS')} - ${this.site.domain}`,
}
},
metaInfo() {
return {
title: `${this.__('DNS')} - ${this.site.domain}`,
}
},
layout: MainLayout,
layout: MainLayout,
components: {
Input,
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
components: {
FormSelect,
Input,
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Pagination,
Tabs,
Table,
TableHead,
TableHeader,
TableRow,
TableBody,
TableData,
EmptyImage,
},
data() {
return {
sending: false,
loading: true,
data() {
return {
sending: false,
loading: true,
action: 'create',
recordIdBeingUpdated: null,
records: [],
records: {},
recordUpdateValidationMessages: {},
form: {
name: null,
content: null,
type: null,
ttl: null,
},
form: {
name: null,
content: null,
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('DNS'),
to: this.route('sites.dns.index', this.site.id),
},
],
}
},
props: {
site: Object,
},
mounted() {
this.getRecords();
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('sites.dns.store', this.site.id), this.form, {
onStart: () => {
this.action = 'create';
this.recordIdBeingUpdated = null;
this.sending = true;
this.records = [];
},
onFinish: () => {
this.sending = false;
this.records = [];
this.getRecords();
this.form = {
name: null,
content: null,
type: null,
ttl: null,
};
}
})
breadcrumbs: [
{
title: this.$page.props.settings.name,
to: '/',
},
{
title: this.__('Sites'),
to: this.route('sites.index'),
},
{
title: this.site.domain,
to: this.route('sites.show', this.site.id),
},
{
title: this.__('DNS'),
to: this.route('sites.dns.index', this.site.id),
},
],
}
},
props: {
site: Object,
},
getRecords() {
this.loading = true;
mounted () {
this.getRecords();
},
methods: {
useNotification,
submit() {
this.$inertia.post(this.route('sites.dns.store', this.site.id), this.form, {
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
this.records = [];
this.getRecords();
this.form = {
name: null,
content: null,
};
}
window.axios.get(this.route('sites.dns.records', this.site.id))
.then(response => {
this.loading = false;
this.records = response.data;
})
},
updateRecordName(event, index) {
alert(event.target.value, index);
},
getRecords() {
this.loading = true;
window.axios.get(this.route('sites.dns.records', this.site.id))
.then(response => {
this.loading = false;
this.records = response.data
g})
.catch(error => {
this.loading = false;
})
},
save(record) {
this.$inertia.put(this.route('sites.dns.update', [this.site.id, record.id]), {
name: record.name,
content: record.content,
}, {
preserveScroll: true,
onStart: () => this.sending = true,
});
},
confirmDelete(record) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your DNS will be completely removed.'),
onConfirm: () => this.delete(record),
.catch(error => {
this.loading = false;
})
},
delete(record) {
this.$inertia.delete(this.route('sites.dns.delete', [this.site.id, record.id]), {
preserveScroll: true,
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
this.records = [];
this.getRecords();
}
})
}
},
}
save(record, index) {
// Vue is not reactive on nested arrays. That's why we cannot use the v-model directive on the input fields,
// because the input fields are part of a v-for.
let type = document.getElementById(record.id + '-type').value;
let name = document.getElementById(record.id + '-name').value;
let content = document.getElementById(record.id + '-content').value;
let ttl = document.getElementById(record.id + '-ttl').value;
this.records[index].type = type;
this.records[index].name = name;
this.records[index].content = content;
this.records[index].ttl = ttl;
this.$inertia.put(this.route('sites.dns.update', [this.site.id, record.id]), {
type: type,
name: name,
content: content,
ttl: ttl,
}, {
preserveScroll: true,
onStart: () => {
this.sending = true;
this.action = 'update';
this.recordIdBeingUpdated = record.id;
},
onFinish: () => {
this.sending = false;
this.getRecords();
}
});
},
confirmDelete(record) {
useConfirm({
title: this.__('Are you sure?'),
message: this.__('Your DNS will be completely removed.'),
onConfirm: () => this.delete(record),
})
},
delete(record) {
this.$inertia.delete(this.route('sites.dns.delete', [this.site.id, record.id]), {
preserveScroll: true,
onStart: () => this.sending = true,
onFinish: () => {
this.sending = false;
this.records = [];
this.getRecords();
}
})
}
},
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<FormGroup class="relative">
<Label :errors="errors" :forId="id">{{ label }}</Label>
<Label :errors="errors" :forId="id" v-if="label">{{ label }}</Label>
<button type="button" @click="copy" v-if="allowCopy" class="flex items-center right-0 absolute text-xs text-medium-emphasis">
<IconClipboard class="mr-2" />
@@ -50,7 +50,7 @@ export default {
},
label: {
type: String,
required: true,
required: false,
},
type: {
type: String,

View File

@@ -1,6 +1,6 @@
<template>
<FormGroup>
<Label :errors="errors" :forId="id">{{ label }}</Label>
<Label :errors="errors" :forId="id" v-if="label">{{ label }}</Label>
<select :disabled="loading || disabled" :class="[
defaultClasses,
disabled || loading ? 'opacity-50' : '',
@@ -32,7 +32,7 @@ export default {
},
label: {
type: String,
required: true,
required: false,
},
type: {
type: String,