diff --git a/public/scripts/records.js b/public/scripts/records.js index 3339e40..3e65d50 100644 --- a/public/scripts/records.js +++ b/public/scripts/records.js @@ -1,117 +1,178 @@ -import { html, Component, render, createContext, useState, useEffect } from './vendor/preact/standalone.js'; +import { html, render, useState, useEffect } from './vendor/preact/standalone.js'; import { getRecords } from './api.js'; const rdataInputProperties = { - Address: {label: 'adresse', type: 'text'}, - Serial: {label: 'serial', 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'}, + Address: {label: 'adresse :', type: 'text'}, + Serial: {label: 'serial :', 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 Editable = createContext(false); +const recordTypes = { + 'A': 'address', + 'AAAA': 'address', + 'SRV': 'service', + 'CNAME': 'alias', + 'NS': 'name_server', + 'SOA': 'soa', +}; - -function RDataInput({ name, value = '', index = 0 }) { - const {label, type} = rdataInputProperties[name] || {label: name, type: 'text'}; - - return html` - <${Editable.Consumer}> - ${ - (editable) => { - if (editable) { - return html` -
- - -
- `; - } else { - return html` -
-
${label}:
-
${value}
-
- `; - } - } - } - - `; +const recordTypeNames = { + 'address': 'Adresse IP', + 'service': 'Service', + 'alias': 'Alias', + 'name_server': 'Serveur de nom', + 'soa': 'SOA', } -function RData({ rdata, index }) { - const { Address: address } = rdata; - return Object.entries(rdata).map(([name, value]) => html`<${RDataInput} name=${name} value=${value} index=${index} />`); +function getNameForRecord(name, type) { + return name; } +function getTypeForRecord(name, type) { + return recordTypes[type]; +} -function Record({name, ttl, type, rdata, index = 0}) { +function processRecords(records) { + return records.reduce((acc, record) => { + let name = getNameForRecord(record.Name, record.Type); + let type = getTypeForRecord(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; + }, {}); +} +const recordsKeys = { + 'address': (record) => { return [[ 'Address', record.Address ]] }, + 'service': (record) => { /* TODO */ }, + 'alias': (record) => { return [[ 'Target', record.Target ]] }, + 'name_server': (record) => { return [[ 'Target', record.Target ]] }, + 'soa': (record) => { + return [ + [ 'MasterServerName', record.MasterServerName ], + [ 'MaintainerName', record.MaintainerName ], + [ 'Refresh', record.Refresh ], + [ 'Retry', record.Retry ], + [ 'Expire', record.Expire ], + [ 'Minimum', record.Minimum ], + [ 'Serial', record.Serial ], + ] + }, +} + +function FriendlyRecord({type, record}) { + let keys = recordsKeys[type](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} - ${type} - ${ttl} - - <${Editable.Consumer}> - ${ - (editable) => { - if (editable) { - return html`<${RData} rdata=${rdata} index=${index}/>` - } else { - return html`
<${RData} rdata=${rdata} index=${index}/>
` - } - } +
+

${name}

+
+ ${Object.entries(recordSets).map( + ([type, records]) => { + return html` +
+

${recordTypeNames[type]}

+
    + ${records.map(record => html`
  • <${FriendlyRecord} type=${type} record=${record}/>
  • `)} +
+
+ `; } - - + )}
- +
`; } -function RecordList({ zone }) { - const [records, setRecords] = useState([]); - const [editable, setEditable] = useState(false); - const toggleEdit = () => setEditable(!editable); + +function RecordListFriendly({ zone }) { + const [records, setRecords] = useState({}); + const [editable, setEditable] = useState(false); useEffect(() => { getRecords(zone) - .then((res) => setRecords(res)); - }, []); - + .then((res) => setRecords(processRecords(res))); + }, [zone]); return html` - <${Editable.Provider} value=${editable}> - - - - - - - - - - - - ${records.map( - ({Name, Class, TTL, Type, ...rdata}, index) => { - return html`<${Record} name=${Name} ttl=${TTL} type=${Type} rdata=${rdata} index=${index}/>` - } - )} - - - + ${Object.entries(records).map( + ([name, recordSets]) => { + return html` + + <${RecordsByName} name=${name} recordSets=${recordSets}/> + `; + } + )} + `; +} + +function NewRecordFormFriendly({ zone }) { + return html` + +
+ +
+ + .${ zone } +
+
+
+ + +
+
+ +
+
+ + +
+ + `; +} + +function ZoneRecords({ zone }) { + const [addNewRecord, setAddNewRecord] = useState(false); + const [newRecords, setNewRecords] = useState([]); + + return html` +
+

Enregistrements

+ + +
+ ${newRecords} + <${RecordListFriendly} zone=${zone} /> `; } export default function(element, { zone }) { - render(html`<${RecordList} zone=${zone} />`, element); + render(html`<${ZoneRecords} zone=${zone} />`, element); }; diff --git a/public/styles/main.css b/public/styles/main.css index 4c80f59..eb1e819 100644 --- a/public/styles/main.css +++ b/public/styles/main.css @@ -3,12 +3,13 @@ body { min-height: 100vh; min-width: 100vw; margin: 0; + font-family: sans-serif; } :root { - --color-primary: #712da0; - --color-hightlight-1: #ffbac6; - --color-hightlight-2: #f560f5; + --color-primary: #5e0c97; + --color-hightlight-1: #ffd4ba; + --color-hightlight-2: #dd39dd; --color-contrast: white; } @@ -23,6 +24,10 @@ h1 { margin-bottom: 1rem; } +a { + color: var(--color-primary) +} + p.feedback { padding: .35rem; margin: 0; @@ -33,13 +38,14 @@ p.feedback.error { color: #710000; } +button, input { padding: .35rem .35rem; font-size: 1rem; } +button, input[type="submit"] { - margin-top: 2rem; background: var(--color-primary); color: var(--color-contrast); border-left: 5px solid var(--color-hightlight-1); @@ -48,12 +54,21 @@ input[type="submit"] { border-bottom: 5px solid var(--color-hightlight-2); } +button:hover, input[type="submit"]:hover { background: #7a43a1; } +button:active , input[type="submit"]:active { - background: #875ba6; + border-left: 5px solid var(--color-hightlight-2); + border-top: 5px solid var(--color-hightlight-2); + border-right: 5px solid var(--color-hightlight-1); + border-bottom: 5px solid var(--color-hightlight-1); +} + +form input[type="submit"] { + margin-top: 2rem; } form label { @@ -71,7 +86,7 @@ nav.main { display: flex; flex: 0; padding: 1rem; - border-right: 5px solid var(--color-hightlight-1); + border-right: 5px solid var(--color-hightlight-2); } nav.main a { diff --git a/public/styles/zone.css b/public/styles/zone.css index 1486cb3..7414673 100644 --- a/public/styles/zone.css +++ b/public/styles/zone.css @@ -12,3 +12,105 @@ nav.secondary li { main { flex-direction: column; } + +main > section { + max-width: 120ch; +} + +header { + display: flex; + margin: 1rem 0; +} + +h2 { + margin: 0; + flex-grow: 1; +} +header > :not(:last-of-type) { + margin-right: 2ch; +} + +zone-content h3, zone-content h4 { + margin: 0; + font-weight: normal; + font-size: 1rem; + width: 30%; +} + +zone-content article { + display: flex; +} + +zone-content > article > div { + flex-grow: 1; +} + +zone-content > article { + margin: .5rem 0; + position: relative; +} + +zone-content > article:not(:last-of-type) { + border-bottom: 2px solid var(--color-hightlight-2); +} + +zone-content article > *{ + margin-right: 2ch; +} + +zone-content article ul { + padding: 0; + margin: 0; + list-style-type: none; +} + +zone-content article dl { + display: grid; + grid-template: auto / max-content 1fr; +} + +zone-content article dd { + margin: 0; +} + +zone-content article dt span { + display: inline-block; + background-color: var(--color-hightlight-1); + padding: 0.1em 0.5em; + border-radius: 0.5em; + margin-right: 0.1rem; + font-size: .7rem; +} + +form.new-record { + display: flex; + flex-direction: row; +} + +form.new-record > div { + display: flex; + flex-direction: column; + margin-right: 2ch; +} + +form.new-record > div:first-child { + width: 30%; +} + +form.new-record > div:first-child span { + flex-grow: 1; + font-size: .8rem; +} + +form.new-record > div:nth-child(2) { + width: calc( .3 * (70% - 4ch)); +} + + +form.new-record > div:nth-child(2) select { + flex-grow: 1; +} + +form.new-record > div:nth-child(3) { + flex: 1; +} diff --git a/src/controllers.rs b/src/controllers.rs index 1a27199..577a4c0 100644 --- a/src/controllers.rs +++ b/src/controllers.rs @@ -13,7 +13,7 @@ pub async fn do_login( auth_request: models::AuthTokenRequest, cookies: &CookieJar<'_> ) -> Result { - let session_duration = config.web_app.token_duration; + let session_duration = config.web_app.token_duration; let session = conn.run(move |c| { let user_info = models::LocalUser::get_user_by_creds( diff --git a/templates/bases/app.html b/templates/bases/app.html index f03a505..e36c1b1 100644 --- a/templates/bases/app.html +++ b/templates/bases/app.html @@ -4,7 +4,7 @@ {% block content %}
-

Enregistrements

NomTypeTTLDonnées