improve add record section
This commit is contained in:
parent
960942c47f
commit
d06d745dcb
2 changed files with 176 additions and 104 deletions
|
@ -13,6 +13,12 @@ const rdataInputProperties = {
|
|||
MasterServerName: {label: 'Serveur primaire', type: 'text'},
|
||||
Expire: {label: 'Expiration', type: 'number'},
|
||||
Target: {label: 'Cible', type: 'text'},
|
||||
Service: {label: 'Service', type: 'text'},
|
||||
Protocol: {label: 'Protocole', type: 'text'},
|
||||
Priority: {label: 'Priorité', type: 'number'},
|
||||
Weight: {label: 'Poids', type: 'number'},
|
||||
Port: {label: 'Port', type: 'number'},
|
||||
Server: {label: 'Serveur', type: 'text'},
|
||||
};
|
||||
|
||||
const realRecordDataConfig = {
|
||||
|
@ -30,7 +36,7 @@ const realRecordDataConfig = {
|
|||
},
|
||||
'SRV': {
|
||||
friendlyType: 'service',
|
||||
fields: [ /* TODO */ ],
|
||||
fields: [ 'Priority', 'Weight', 'Port', 'Server' ],
|
||||
},
|
||||
'NS': {
|
||||
friendlyType: 'name_server',
|
||||
|
@ -43,38 +49,84 @@ const realRecordDataConfig = {
|
|||
};
|
||||
|
||||
function defaultBuildData(realRecordType) {
|
||||
const defaultFields = realRecordDataConfig[realRecordType].fields.map(field => [field, null]);
|
||||
const defaultFields = Object.fromEntries(realRecordDataConfig[realRecordType].fields.map(field => [field, null]));
|
||||
return (fields) => {
|
||||
return {...defaultFields, ...fields, Type: realRecordType};
|
||||
}
|
||||
}
|
||||
|
||||
function defaultRecordToField(realRecord) {
|
||||
function defaultRecordToFields(realRecord) {
|
||||
const type = realRecord.Type;
|
||||
return realRecordDataConfig[type].fields.map(field => [field, realRecord[field]]);
|
||||
}
|
||||
|
||||
function defaultGetName(name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
function srvRecordToFields({ Name, Type, Class, ...fields }) {
|
||||
const [ serviceName, protocol] = Name.split('.');
|
||||
return {
|
||||
Service: serviceName.replace(/^_/, ''),
|
||||
Protocol: protocol.replace(/^_/, ''),
|
||||
...fields
|
||||
}
|
||||
}
|
||||
|
||||
function srvGetName(originalName) {
|
||||
const [_serviceName, _protocol, ...name] = originalName.split('.');
|
||||
return name.join('.');
|
||||
}
|
||||
|
||||
function buildAddressRecord(fields) {
|
||||
const address = fields.Address || '';
|
||||
if (address.indexOf('.') >= 0) {
|
||||
fields.Type = 'A';
|
||||
} else if (address.indexOf(':') >= 0) {
|
||||
fields.Type = 'AAAA';
|
||||
} else {
|
||||
fields.Type = '';
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
function buildServiceRecord({ Name, Service, Protocol, ...fields}) {
|
||||
fields.Name = `_${Service}._${Protocol}.${Name}`;
|
||||
fields.Type = 'SRV';
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
const friendlyRecordDataConfig = {
|
||||
'address': {
|
||||
realRecordToFields: defaultRecordToField,
|
||||
realRecordToFields: defaultRecordToFields,
|
||||
fields: realRecordDataConfig['AAAA'].fields,
|
||||
buildData: buildAddressRecord,
|
||||
getName: defaultGetName,
|
||||
},
|
||||
'alias': {
|
||||
realRecordToFields: defaultRecordToField,
|
||||
realRecordToFields: defaultRecordToFields,
|
||||
fields: realRecordDataConfig['CNAME'].fields,
|
||||
buildData: defaultBuildData('CNAME'),
|
||||
getName: defaultGetName,
|
||||
},
|
||||
'name_server': {
|
||||
realRecordToFields: defaultRecordToField,
|
||||
realRecordToFields: defaultRecordToFields,
|
||||
fields: realRecordDataConfig['CNAME'].fields,
|
||||
buildData: defaultBuildData('NS'),
|
||||
getName: defaultGetName,
|
||||
},
|
||||
'soa': {
|
||||
realRecordToFields: defaultRecordToField,
|
||||
realRecordToFields: defaultRecordToFields,
|
||||
fields: realRecordDataConfig['SOA'].fields,
|
||||
buildData: defaultBuildData('SOA'),
|
||||
getName: defaultGetName,
|
||||
},
|
||||
'service': {
|
||||
realRecordToFields: srvRecordToFields,
|
||||
fields: ['Service', 'Protocol', 'Priority', 'Weight', 'Port', 'Server'],
|
||||
buildData: buildServiceRecord,
|
||||
getName: srvGetName,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -87,11 +139,6 @@ const recordTypeNames = {
|
|||
'soa': 'SOA',
|
||||
}
|
||||
|
||||
/* Type to use for SRV to derive name without port / service */
|
||||
function getNameForRecord(name, type) {
|
||||
return name;
|
||||
}
|
||||
|
||||
/* Name to use with _spf for example */
|
||||
function getFriendlyTypeForRecord(name, type) {
|
||||
return realRecordDataConfig[type].friendlyType;
|
||||
|
@ -99,8 +146,8 @@ function getFriendlyTypeForRecord(name, type) {
|
|||
|
||||
function processRecords(records) {
|
||||
return records.reduce((acc, record) => {
|
||||
let name = getNameForRecord(record.Name, record.Type);
|
||||
let type = getFriendlyTypeForRecord(record.Name, record.Type);
|
||||
let name = friendlyRecordDataConfig[type].getName(record.Name);
|
||||
if (!(name in acc)) {
|
||||
acc[name] = {};
|
||||
}
|
||||
|
@ -112,13 +159,6 @@ function processRecords(records) {
|
|||
}, {});
|
||||
}
|
||||
|
||||
function buildAddressRecord({ Address = ''}) {
|
||||
return {
|
||||
Type: Address.indexOf(':') > -1 ? 'AAAA' : 'A',
|
||||
Address
|
||||
}
|
||||
}
|
||||
|
||||
function FriendlyRecord({type, record}) {
|
||||
let keys = friendlyRecordDataConfig[type].realRecordToFields(record);
|
||||
if (keys.length == 1) {
|
||||
|
@ -154,8 +194,6 @@ function RecordsByName({ name, recordSets }) {
|
|||
`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function RecordListFriendly({ zone }) {
|
||||
const [records, setRecords] = useState({});
|
||||
const [editable, setEditable] = useState(false);
|
||||
|
@ -176,19 +214,21 @@ function RecordListFriendly({ zone }) {
|
|||
`;
|
||||
}
|
||||
|
||||
function NewRecordFormFriendly({ zone, enabled, id, onCancel }) {
|
||||
function NewRecordFormFriendly({ zone }) {
|
||||
const defaultVaules = {Name: '', TTL: 3600, Class: 'IN'};
|
||||
const [recordType, setRecordType] = useState(Object.keys(recordTypeNames)[0]);
|
||||
const [recordData, setRecordData] = useState({});
|
||||
const [recordData, setRecordData] = useState(defaultVaules);
|
||||
const [realRecordData, setRealRecordData] = useState({});
|
||||
const [realType, setRealType] = useState('');
|
||||
const [domain, setDomain] = useState('');
|
||||
const [ttl, setTTL] = useState(3600);
|
||||
|
||||
const absoluteName = (name) => name ? `${name}.${zone}` : zone;
|
||||
|
||||
const setRecordDataFactory = (field) => {
|
||||
return (e) => {
|
||||
const newData = {...recordData};
|
||||
newData[field] = e.target.value;
|
||||
const newRealRecordData = friendlyRecordDataConfig[recordType].buildData(newData)
|
||||
newData[field] = e.target.type == 'number' ? Number(e.target.value) : e.target.value;
|
||||
const newRealRecordData = friendlyRecordDataConfig[recordType].buildData({...newData, Class: 'IN', Name: absoluteName(newData.Name)})
|
||||
|
||||
setRecordData(newData);
|
||||
setRealRecordData(newRealRecordData);
|
||||
setRealType(newRealRecordData.Type);
|
||||
|
@ -197,75 +237,90 @@ function NewRecordFormFriendly({ zone, enabled, id, onCancel }) {
|
|||
|
||||
const createNewRecord = (e) => {
|
||||
e.preventDefault();
|
||||
const newRecords = [{...realRecordData, Class: 'IN', TTL: ttl, Name: `${domain}.${zone}`}];
|
||||
const newRecords = [realRecordData];
|
||||
console.log(newRecords)
|
||||
createRecords(zone, newRecords);
|
||||
}
|
||||
|
||||
// TODO: Reset valeurs champs quand changement de type => bound la valeur de l'input au state
|
||||
const resetData = (resetName = false) => {
|
||||
setRealType('');
|
||||
const newName = resetName ? defaultVaules.Name : recordData.Name;
|
||||
setRecordData({ Name: newName, TTL: defaultVaules.TTL });
|
||||
setRealRecordData({...defaultVaules, Name: absoluteName(newName)});
|
||||
}
|
||||
|
||||
useEffect(() => resetData(true), []);
|
||||
|
||||
// TODO: Reset valeurs champs quand changement de type + "annuler" => bound la valeur de l'input au state
|
||||
// TODO: Dans le cas où un domain est dans le RDATA mettre le domaine absolue dans la preview
|
||||
// TODO: Déplacer preview dans son component, faire une vue en "diff" et l'appeler "prévisualisation des changements"
|
||||
// TODO: Validation des données client et serveur
|
||||
|
||||
return html`
|
||||
<form class="new-record ${enabled ? '' : 'disabled'}" id=${id}>
|
||||
<h3>Nouvel enregistrement</h3>
|
||||
<div class="form-row">
|
||||
<div class="input-group">
|
||||
<label for="domain">Domaine</label>
|
||||
<div class="combined-input">
|
||||
<input type="text" id="domain" name="domain" onChange=${e => setDomain(e.target.value)}/>
|
||||
<span>.${ zone }</span>
|
||||
<section class="new-record">
|
||||
<header>
|
||||
<h2>Nouvel enregistrement</h2>
|
||||
</header>
|
||||
<form>
|
||||
<div class="form-row">
|
||||
<div class="input-group">
|
||||
<label for="domain">Domaine</label>
|
||||
<div class="combined-input">
|
||||
<input type="text" id="domain" name="domain" value=${recordData.Name} onInput=${setRecordDataFactory('Name')}/>
|
||||
<span>.${ zone }</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="record_type">Type d'enregistrement</label>
|
||||
<select id="record_type" name="record_type" onChange=${(e) => { setRecordType(e.target.value); resetData() }}>
|
||||
${Object.entries(recordTypeNames).map(([type, name]) => html`<option value="${type}">${name}</option>`)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
${friendlyRecordDataConfig[recordType].fields.map(fieldName => html`
|
||||
<div class="input-group">
|
||||
<label for="${fieldName}">${rdataInputProperties[fieldName].label}</label>
|
||||
<input id="${fieldName}" type="${rdataInputProperties[fieldName].type}" onInput=${setRecordDataFactory(fieldName)}></input>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="ttl">Durée dans le cache</label>
|
||||
<input type="number" name="ttl" id="ttl" value=${recordData.TTL} onInput=${setRecordDataFactory('TTL')}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="record_type">Type d'enregistrement</label>
|
||||
<select id="record_type" name="record_type" onChange=${e => { setRecordType(e.target.value); setRealType(''); setRecordData({}); setRealRecordData({})}}>
|
||||
${Object.entries(recordTypeNames).map(([type, name]) => html`<option value="${type}">${name}</option>`)}
|
||||
</select>
|
||||
</div>
|
||||
<article class="preview">
|
||||
<h3>Prévisualisation des changements</h3>
|
||||
<p>
|
||||
<img src="/images/plus.svg" alt="Ajout" title="Ajout" class="addition"/>
|
||||
<code class="addition">
|
||||
${realRecordData.Name === zone ? '@' : realRecordData.Name} ${realRecordData.TTL} ${realRecordData.Class} ${realType} ${realType != '' ? realRecordDataConfig[realType].fields.map(field => realRecordData[field]).join(' ') : ''}
|
||||
</code>
|
||||
</p>
|
||||
</article>
|
||||
<div>
|
||||
${friendlyRecordDataConfig[recordType].fields.map(fieldName => html`
|
||||
<div class="input-group">
|
||||
<label for="${fieldName}">${rdataInputProperties[fieldName].label}</label>
|
||||
<input id="${fieldName}" type="${rdataInputProperties[fieldName].type}" onChange=${setRecordDataFactory(fieldName)}></input>
|
||||
</div>
|
||||
`)}
|
||||
<input type="submit" onClick=${createNewRecord} value="Ajouter"/>
|
||||
<button type="reset" onClick=${e => { resetData(true); e.preventDefault() }}>Annuler</button>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="ttl">TTL</label>
|
||||
<input type="number" name="ttl" id="ttl" value="${ttl}" onChange=${e => setTTL(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<article class="preview">
|
||||
<h4>Prévisualisation de l'enregistrement</h3>
|
||||
<pre>
|
||||
${domain == '' ? '@' : domain + '.' + zone} ${ttl} IN ${realType} ${realType != '' ? realRecordDataConfig[realType].fields.map(field => realRecordData[field]).join(' ') : ''}
|
||||
</pre>
|
||||
</article>
|
||||
<div>
|
||||
<input type="submit" onClick=${createNewRecord} value="Ajouter"/>
|
||||
<button role="button" onClick=${onCancel}>Anuler</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function ZoneRecords({ zone }) {
|
||||
const [addNewRecord, setAddNewRecord] = useState(true);
|
||||
|
||||
const onCancelNewRecord = (e) => { setAddNewRecord(false); e.preventDefault() };
|
||||
|
||||
return html`
|
||||
<header>
|
||||
<h2>Enregistrements</h2>
|
||||
<button onClick=${() => setAddNewRecord(true)} aria-controls="add-new-record-form" aria-expanded=${addNewRecord} disabled=${addNewRecord}>Ajouter un enregistrement</button>
|
||||
<button>Éditer la zone</button>
|
||||
</header>
|
||||
<${NewRecordFormFriendly} zone=${zone} enabled=${addNewRecord} id="add-new-record-form" onCancel=${onCancelNewRecord}/>
|
||||
<div class="zone-content">
|
||||
<${RecordListFriendly} zone=${zone} />
|
||||
</div>
|
||||
<${NewRecordFormFriendly} zone=${zone}/>
|
||||
|
||||
<section>
|
||||
<header>
|
||||
<h2>Contenu de la zone</h2>
|
||||
<button>Éditer la zone</button>
|
||||
</header>
|
||||
|
||||
<div class="zone-content">
|
||||
<${RecordListFriendly} zone=${zone} />
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ header > :not(:last-of-type) {
|
|||
|
||||
.zone-content article {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.zone-content > article > div {
|
||||
|
@ -78,26 +79,22 @@ header > :not(:last-of-type) {
|
|||
font-size: .7rem;
|
||||
}
|
||||
|
||||
form.new-record {
|
||||
.new-record form {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
form.new-record > div.form-row > div:not(:last-child) {
|
||||
.new-record form > div.form-row > * {
|
||||
flex-grow: 1;
|
||||
margin-right: 2ch;
|
||||
}
|
||||
|
||||
form.new-record > div.form-row {
|
||||
.new-record form > div.form-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2ch;
|
||||
}
|
||||
|
||||
form.new-record > div.form-row > div:first-child {
|
||||
min-width: 30%;
|
||||
}
|
||||
|
||||
form.new-record > div.form-row > div:nth-child(2) {
|
||||
min-width: calc( .3 * (70% - 4ch));
|
||||
.new-record form label {
|
||||
margin-top: .25rem;
|
||||
}
|
||||
|
||||
form div.input-group {
|
||||
|
@ -126,32 +123,52 @@ form.disabled {
|
|||
display: none;
|
||||
}
|
||||
|
||||
input[name^="ttl"] {
|
||||
max-width: 10ch;
|
||||
}
|
||||
|
||||
form.new-record button,
|
||||
form.new-record input[type="submit"] {
|
||||
.new-record form button,
|
||||
.new-record form input[type="submit"] {
|
||||
margin-right: 1ch;
|
||||
margin-top: .75rem;
|
||||
}
|
||||
form.new-record h3 {
|
||||
margin: 0;
|
||||
|
||||
.new-record header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
form.new-record .preview {
|
||||
|
||||
.new-record form .preview {
|
||||
margin: .5rem 0;
|
||||
border: 1px solid rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
form.new-record .preview pre {
|
||||
margin: .5rem 0 .5rem 0;
|
||||
padding: 0 .5rem;
|
||||
.new-record form .preview p:first-of-type {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
form.new-record .preview h4 {
|
||||
.new-record form .preview p:last-of-type {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.new-record form .preview p {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.new-record form .preview code {
|
||||
padding: 0 .5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.new-record form .preview img {
|
||||
padding: 0 .25rem;
|
||||
border-right: 1px solid #1b841b;
|
||||
}
|
||||
|
||||
.new-record form .preview .addition {
|
||||
background: #d9fbd9;
|
||||
}
|
||||
|
||||
.new-record form .preview h3 {
|
||||
margin: 0;
|
||||
padding: .0rem .5rem 0 .5rem;;
|
||||
border-bottom: 1px solid rgb(var(--color-primary));
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
background: rgb(var(--color-primary));
|
||||
color: var(--color-contrast)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue