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`

Nouvel enregistrement

setDomain(e.target.value)}/> .${ zone }
${friendlyRecordDataConfig[recordType].fields.map(fieldName => html`
`)}
setTTL(e.target.value)}/>

Prévisualisation de l'enregistrement

                    ${domain == '' ? '@' : domain + '.' + zone} ${ttl} IN ${realType} ${realType != '' ? realRecordDataConfig[realType].fields.map(field => realRecordData[field]).join(' ') : ''}
                
`; } 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); };