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'}, 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 = { 'A': { friendlyType: 'address', fields: ['Address'], }, 'AAAA': { friendlyType: 'address', fields: ['Address'], }, 'CNAME': { friendlyType: 'alias', fields: ['Target'], }, 'SRV': { friendlyType: 'service', fields: [ 'Priority', 'Weight', 'Port', 'Server' ], }, 'NS': { friendlyType: 'name_server', fields: ['Target'], }, 'SOA': { friendlyType: 'soa', fields: ['MasterServerName', 'MaintainerName', 'Refresh', 'Retry', 'Expire', 'Minimum', 'Serial'], }, }; function defaultBuildData(realRecordType) { const defaultFields = Object.fromEntries(realRecordDataConfig[realRecordType].fields.map(field => [field, null])); return (fields) => { return {...defaultFields, ...fields, Type: realRecordType}; } } 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: defaultRecordToFields, fields: realRecordDataConfig['AAAA'].fields, buildData: buildAddressRecord, getName: defaultGetName, }, 'alias': { realRecordToFields: defaultRecordToFields, fields: realRecordDataConfig['CNAME'].fields, buildData: defaultBuildData('CNAME'), getName: defaultGetName, }, 'name_server': { realRecordToFields: defaultRecordToFields, fields: realRecordDataConfig['NS'].fields, buildData: defaultBuildData('NS'), getName: defaultGetName, }, 'soa': { 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, }, }; const recordTypeNames = { 'address': 'Adresse IP', 'service': 'Service', 'alias': 'Alias', 'name_server': 'Serveur de nom', 'soa': 'SOA', } /* Name to use with spf for example */ function getFriendlyTypeForRecord(name, type) { return realRecordDataConfig[type].friendlyType; } function processRecords(records) { return records.reduce((acc, record) => { let type = getFriendlyTypeForRecord(record.Name, record.Type); let name = friendlyRecordDataConfig[type].getName(record.Name); if (!(name in acc)) { acc[name] = {}; } if (!(type in acc[name])) { acc[name][type] = []; } acc[name][type].push(record); return acc; }, {}); } 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 }) { const defaultVaules = {Name: '', TTL: 3600, Class: 'IN'}; const [recordType, setRecordType] = useState(Object.keys(recordTypeNames)[0]); const [recordData, setRecordData] = useState(defaultVaules); const [realRecordData, setRealRecordData] = useState({}); const [realType, setRealType] = useState(''); const absoluteName = (name) => name ? `${name}.${zone}` : zone; const setRecordDataFactory = (field) => { return (e) => { const newData = {...recordData}; 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); } } const createNewRecord = (e) => { e.preventDefault(); const newRecords = [realRecordData]; console.log(newRecords) createRecords(zone, newRecords); } 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`

Nouvel enregistrement

.${ zone }
${friendlyRecordDataConfig[recordType].fields.map(fieldName => html`
`)}

Prévisualisation des changements

Ajout ${realRecordData.Name === zone ? '@' : realRecordData.Name} ${realRecordData.TTL} ${realRecordData.Class} ${realType} ${realType != '' ? realRecordDataConfig[realType].fields.map(field => realRecordData[field]).join(' ') : ''}

`; } function ZoneRecords({ zone }) { return html` <${NewRecordFormFriendly} zone=${zone}/>

Contenu de la zone

<${RecordListFriendly} zone=${zone} />
`; } export default function(element, { zone }) { render(html`<${ZoneRecords} zone=${zone} />`, element); };