import { html, render, useState, useEffect } from './vendor/preact/standalone.js';
import { getRecords, createRecords } from './api.js';
const rdataInputProperties = {
Address: {label: 'Adresse', type: 'text'},
Serial: {label: 'Numéro de série', type: 'number'},
Minimum: {label: 'Minimum', type: 'number'},
Retry: {label: 'Nouvelle tentative', type: 'number'},
Refresh: {label: 'Actualisation', type: 'number'},
MaintainerName: {label: 'Contact', type: 'text'},
MasterServerName: {label: 'Serveur primaire', type: 'text'},
Expire: {label: 'Expiration', type: 'number'},
Target: {label: 'Cible', type: 'text'},
};
const realRecordDataConfig = {
'A': {
friendlyType: 'address',
fields: ['Address'],
},
'AAAA': {
friendlyType: 'address',
fields: ['Address'],
},
'CNAME': {
friendlyType: 'alias',
fields: ['Target'],
},
'SRV': {
friendlyType: 'service',
fields: [ /* TODO */ ],
},
'NS': {
friendlyType: 'name_server',
fields: ['Target'],
},
'SOA': {
friendlyType: 'soa',
fields: ['MasterServerName', 'MaintainerName', 'Refresh', 'Retry', 'Expire', 'Minimum', 'Serial'],
},
};
function defaultBuildData(realRecordType) {
const defaultFields = realRecordDataConfig[realRecordType].fields.map(field => [field, null]);
return (fields) => {
return {...defaultFields, ...fields, Type: realRecordType};
}
}
function defaultRecordToField(realRecord) {
const type = realRecord.Type;
return realRecordDataConfig[type].fields.map(field => [field, realRecord[field]]);
}
const friendlyRecordDataConfig = {
'address': {
realRecordToFields: defaultRecordToField,
fields: realRecordDataConfig['AAAA'].fields,
buildData: buildAddressRecord,
},
'alias': {
realRecordToFields: defaultRecordToField,
fields: realRecordDataConfig['CNAME'].fields,
buildData: defaultBuildData('CNAME'),
},
'name_server': {
realRecordToFields: defaultRecordToField,
fields: realRecordDataConfig['CNAME'].fields,
buildData: defaultBuildData('NS'),
},
'soa': {
realRecordToFields: defaultRecordToField,
fields: realRecordDataConfig['SOA'].fields,
buildData: defaultBuildData('SOA'),
},
};
const recordTypeNames = {
'address': 'Adresse IP',
'service': 'Service',
'alias': 'Alias',
'name_server': 'Serveur de nom',
'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;
}
function processRecords(records) {
return records.reduce((acc, record) => {
let name = getNameForRecord(record.Name, record.Type);
let type = getFriendlyTypeForRecord(record.Name, record.Type);
if (!(name in acc)) {
acc[name] = {};
}
if (!(type in acc[name])) {
acc[name][type] = [];
}
acc[name][type].push(record);
return acc;
}, {});
}
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) {
return html`${keys[0][1]}`;
} else {
return html`
${keys.map(([name, value]) => {return html`- ${rdataInputProperties[name].label}
- ${value}
`})}
`;
}
}
function RecordsByName({ name, recordSets }) {
return html`
${name}
${Object.entries(recordSets).map(
([type, records]) => {
return html`
${recordTypeNames[type]}
${records.map(record => html`- <${FriendlyRecord} type=${type} record=${record}/>
`)}
`;
}
)}
`;
}
function RecordListFriendly({ zone }) {
const [records, setRecords] = useState({});
const [editable, setEditable] = useState(false);
useEffect(() => {
getRecords(zone)
.then((res) => setRecords(processRecords(res)));
}, [zone]);
return html`
${Object.entries(records).map(
([name, recordSets]) => {
return html`
<${RecordsByName} name=${name} recordSets=${recordSets}/>
`;
}
)}
`;
}
function NewRecordFormFriendly({ zone, enabled, id, onCancel }) {
const [recordType, setRecordType] = useState(Object.keys(recordTypeNames)[0]);
const [recordData, setRecordData] = useState({});
const [realRecordData, setRealRecordData] = useState({});
const [realType, setRealType] = useState('');
const [domain, setDomain] = useState('');
const [ttl, setTTL] = useState(3600);
const setRecordDataFactory = (field) => {
return (e) => {
const newData = {...recordData};
newData[field] = e.target.value;
const newRealRecordData = friendlyRecordDataConfig[recordType].buildData(newData)
setRecordData(newData);
setRealRecordData(newRealRecordData);
setRealType(newRealRecordData.Type);
}
}
const createNewRecord = (e) => {
e.preventDefault();
const newRecords = [{...realRecordData, Class: 'IN', TTL: ttl, Name: `${domain}.${zone}`}];
console.log(newRecords)
createRecords(zone, newRecords);
}
// TODO: Reset valeurs champs quand changement de type => 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`
`;
}
function ZoneRecords({ zone }) {
const [addNewRecord, setAddNewRecord] = useState(true);
const onCancelNewRecord = (e) => { setAddNewRecord(false); e.preventDefault() };
return html`
Enregistrements
<${NewRecordFormFriendly} zone=${zone} enabled=${addNewRecord} id="add-new-record-form" onCancel=${onCancelNewRecord}/>
<${RecordListFriendly} zone=${zone} />
`;
}
export default function(element, { zone }) {
render(html`<${ZoneRecords} zone=${zone} />`, element);
};