/* Graphical installation settings. The server validates the resolved host configuration. */ const installationForm = (() => { const clone = value => JSON.parse(JSON.stringify(value)); const object = (value, label) => { if (value === undefined) return {}; if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label}: Erwartet werden benannte Einstellungen.`); return value; }; const keyboards = [ ['de','Deutsch'],['de-ch','Deutsch (Schweiz)'],['dk','Dänisch'],['en-gb','Englisch (Großbritannien)'], ['en-us','Englisch (USA)'],['es','Spanisch'],['fi','Finnisch'],['fr','Französisch'], ['fr-be','Französisch (Belgien)'],['fr-ca','Französisch (Kanada)'],['fr-ch','Französisch (Schweiz)'], ['hu','Ungarisch'],['is','Isländisch'],['it','Italienisch'],['jp','Japanisch'],['lt','Litauisch'], ['mk','Mazedonisch'],['nl','Niederländisch'],['no','Norwegisch'],['pl','Polnisch'], ['pt','Portugiesisch'],['pt-br','Portugiesisch (Brasilien)'],['se','Schwedisch'],['si','Slowenisch'],['tr','Türkisch'] ]; const countryCodes = 'AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT ZA ZM ZW'.split(' '); const countries = (() => { const names = typeof Intl.DisplayNames === 'function' ? new Intl.DisplayNames(['de'],{type:'region'}) : null; return countryCodes.map(code => [code.toLowerCase(),names?.of(code) || ({DE:'Deutschland',AT:'Österreich',CH:'Schweiz'}[code] || code)]) .sort((a,b) => a[1].localeCompare(b[1],'de')); })(); const timezones = (() => { const regions = {Africa:'Afrika',America:'Amerika',Antarctica:'Antarktis',Arctic:'Arktis',Asia:'Asien',Atlantic:'Atlantik',Australia:'Australien',Europe:'Europa',Indian:'Indischer Ozean',Pacific:'Pazifik'}; const supported = typeof Intl.supportedValuesOf === 'function' ? Intl.supportedValuesOf('timeZone') : []; return [...new Set(['Europe/Berlin','UTC',...supported])].map(zone => [zone,zone === 'UTC' ? 'UTC (koordinierte Weltzeit)' : zone.split('/').map((part,index) => index === 0 ? regions[part] || part : part.replaceAll('_',' ')).join(' / ')]) .sort((a,b) => a[1].localeCompare(b[1],'de')); })(); const interfaces = [ ['ID_NET_NAME_MAC','Name aus MAC-Adresse'], ['ID_NET_NAME_PATH','Name aus Gerätepfad'], ['ID_NET_NAME_SLOT','Name des Steckplatzes'], ['ID_NET_NAME_ONBOARD','Name der Onboard-Schnittstelle'], ['INTERFACE','Schnittstellenname'], ['ID_PATH','Gerätepfad'], ['ID_NET_DRIVER','Treiber'] ]; const raids = [['raid0','RAID0 / Einzelplatte'],['raid1','RAID1 / Spiegel'],['raid10','RAID10'],['raidz-1','RAIDZ1'],['raidz-2','RAIDZ2'],['raidz-3','RAIDZ3']]; const zfsNumbers = [['ashift','Sektorgröße (ashift)',9,16,true],['arc-max','Maximaler ARC in MiB',64,1048576,true],['copies','Datenkopien',1,3,true],['hdsize','Verwendete Plattengröße in GiB',2,1000000,false]]; const lvmNumbers = [['hdsize','Verwendete Plattengröße in GiB',2],['swapsize','Swap-Größe in GiB',0],['maxroot','Maximale Root-Größe in GiB',2],['maxvz','Maximale Daten-Größe in GiB',0],['minfree','Freier Platz in GiB',0]]; const optionsFor = (entries, current, blank = 'Nicht festlegen') => { const items = entries.map(item => Array.isArray(item) ? {value:item[0],label:item[1]} : {value:item,label:item}); if (current !== undefined && current !== '' && !items.some(item => String(item.value) === String(current))) items.push({value:current,label:`Bestehender Wert: ${current}`}); return [{value:'',label:blank}, ...items]; }; function root(container, prefix) { const nodes = [...(container.matches?.('[data-installation-editor]') ? [container] : []), ...container.querySelectorAll('[data-installation-editor]')]; const found = nodes.find(node => node.dataset.installationEditor === prefix); if (!found) throw new Error('Das Installationsformular wurde nicht gefunden. Bitte erneut öffnen.'); return found; } function text(value, label) { if (value === undefined) return ''; if (typeof value !== 'string') throw new Error(`${label}: Der gespeicherte Wert muss Text sein.`); return value; } function strings(value, label) { if (value === undefined) return []; if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) throw new Error(`${label}: Die gespeicherten Einträge müssen Text sein.`); return value; } function numeric(value, label) { if (value === undefined) return ''; if (typeof value !== 'number' || !Number.isFinite(value)) throw new Error(`${label}: Der gespeicherte Wert muss eine Zahl sein.`); return value; } function networkRow(prefix, key = 'ID_NET_NAME_MAC', value = '') { const mac = key === 'ID_NET_NAME_MAC' && (!value || /^enx[0-9a-f]{12}$/.test(value)); const known = interfaces.some(item => item[0] === key); const selected = mac ? '__mac' : known ? key : '__custom'; if (mac && value) value = value.slice(3).match(/../g).join(':'); return `
${field(`${prefix}.interface-key`,'Schnittstelle erkennen über',selected,{type:'select',options:optionsFor([['__mac','MAC-Adresse'],...interfaces,['__custom','Anderes Merkmal']],selected)})} ${field(`${prefix}.interface-value`,'MAC-Adresse oder erwarteter Wert',text(value,'Schnittstellenmerkmal'),{placeholder:mac?'02:00:00:00:00:01':'Erwarteter Merkmalswert'})}
${field(`${prefix}.interface-custom`,'Name des anderen Merkmals',known?'':key,{full:true})}
`; } function render(values = {}, options = {}) { const prefix = options.prefix || 'installation', override = Boolean(options.override); values = clone(object(values,'Installationskonfiguration')); const fresh = !override && Object.keys(values).length === 0; if (fresh) values = {global:{keyboard:'de',country:'de',timezone:'Europe/Berlin'},network:{source:'from-answer'},disk_setup:{filesystem:'zfs',selection:'all',zfs:{raid:'raid0'}}}; const global = object(values.global,'Allgemeine Einstellungen'), network = object(values.network,'Managementnetz'); const disks = object(values.disk_setup,'Datenträger'), zfs = object(disks.zfs,'ZFS'), lvm = object(disks.lvm,'LVM'); const netFilter = object(network.filter,'Schnittstellenmerkmale'), diskFilter = object(disks.filter,'Datenträgerfilter'); if (Object.keys(diskFilter).length > 1) throw new Error('Die Datenträgerauswahl enthält mehrere Filter. Es wird genau ein Seriennummern- oder WWN-Filter unterstützt.'); if (global['reboot-on-error'] !== undefined && typeof global['reboot-on-error'] !== 'boolean') throw new Error('Neustart bei Fehler: Der gespeicherte Wert muss Ja oder Nein sein.'); const input = (name,label,value,settings={}) => field(`${prefix}.${name}`,label,text(value,label),{...settings}); const select = (name,label,value,entries,settings={}) => input(name,label,value,{type:'select',options:optionsFor(entries,value,override?'Vom Profil übernehmen':'Nicht festlegen'),...settings}); const number = (name,label,value,min,max,integer=false) => field(`${prefix}.${name}`,label,numeric(value,label),{type:'number',min,max,hint:'Leer lassen: vorhandene Vorgabe verwenden.'}).replace('type="number"',`type="number" step="${integer?'1':'any'}"`); const section = (name,title,body) => `
${override?field(`${prefix}.enable.${name}`,`${title} für diesen Host festlegen`,Object.hasOwn(values,name),{type:'checkbox',full:true}):`

${esc(title)}

`}
${body}
`; const required = !override; const secretId = text(values.root_secret_id,'Root-Geheimnis'); const secrets = (options.secrets || []).map(secret => [secret.id, secret.name || secret.id]); if (secretId && !secrets.some(secret => secret[0] === secretId)) secrets.push([secretId,`Bisherige Referenz (${secretId})`]); const diskMode = disks.selection !== undefined ? disks.selection : Object.keys(disks).some(key => ['filter','filter_match','expected_serials','expected_count'].includes(key)) ? 'filtered' : ''; const diskKey = Object.keys(diskFilter)[0] || (override ? '' : 'ID_SERIAL_SHORT'); const diskPattern = diskKey ? diskFilter[diskKey] : undefined; const networkRows = Object.entries(netFilter).map(([key,value]) => networkRow(prefix,key,value)).join('') || (override?'':networkRow(prefix)); const reboot = global['reboot-on-error'] === undefined ? '' : String(global['reboot-on-error']); const general = select('global.keyboard','Tastatur',global.keyboard,keyboards,{required}) + select('global.country','Land',global.country,countries,{required}) + select('global.timezone','Zeitzone',global.timezone,timezones,{required}) + input('global.mailto','E-Mail für Systemmeldungen',global.mailto,{required,type:'email'}) + input('global.fqdn','Hostname-Vorgabe (optional)',global.fqdn,{hint:'Der tatsächlich zugewiesene Server liefert seinen vollständigen Hostnamen.'}) + select('global.reboot-on-error','Bei Installationsfehler neu starten',reboot,[['false','Nein'],['true','Ja']]) + dataEditor(`${prefix}.global.root-ssh-keys`,strings(global['root-ssh-keys'],'SSH-Schlüssel'),{label:'Öffentliche SSH-Schlüssel für root',schema:{type:'array',description:'Für jeden öffentlichen Schlüssel einen Eintrag hinzufügen. Keine privaten Schlüssel hinterlegen.',items:{type:'string',title:'Öffentlicher SSH-Schlüssel',minLength:1}}}); const networking = select('network.source','Netzwerkkonfiguration',network.source,[['from-answer','Festes Managementnetz aus diesem Profil']],{required}) + input('network.gateway','Standardgateway',network.gateway,{required}) + input('network.dns','DNS-Server',network.dns,{required}) + input('network.cidr','Management-IP mit Präfix (optional)',network.cidr,{hint:'Die tatsächliche IP mit Präfix kommt aus dem Serverinventar.'}) + `

Management-Schnittstelle

Ein oder mehrere Merkmale der gewünschten Netzwerkkarte. Der Installer verwendet diese zur Auswahl.

${networkRows}
`; const filtering = select('disk.filter.key','Datenträger erkennen über',diskKey,[['ID_SERIAL_SHORT','Seriennummer'],['ID_SERIAL','Vollständige Serienkennung'],['ID_WWN','WWN']],{required}) + input('disk.filter.value','Seriennummer oder Suchmuster',diskPattern,{required,hint:'Ein konkreter Wert oder ein Muster für die unten bestätigten Kennungen.'}) + select('disk.filter_match','Filterverknüpfung',disks.filter_match,[['all','Alle Merkmale'],['any','Mindestens ein Merkmal']]) + number('disk.expected_count','Anzahl bestätigter Datenträger',disks.expected_count,1,16,true) + dataEditor(`${prefix}.disk.expected_serials`,strings(disks.expected_serials,'Datenträgerkennungen'),{label:'Bestätigte Seriennummern oder WWNs',schema:{type:'array',description:'Jeden bestätigten Datenträger als eigenen Eintrag hinzufügen. Die Anzahl wird aus der Liste übernommen, wenn das Anzahlfeld leer bleibt; die tatsächliche Hardware wird dadurch nicht gezählt.',items:{type:'string',title:'Seriennummer oder WWN',minLength:1}}}); const zfsFields = select('disk.zfs.raid','ZFS-Verbund',zfs.raid,raids,{required}) + zfsNumbers.map(([key,label,min,max,integer]) => number(`disk.zfs.${key}`,label,zfs[key],min,max,integer)).join('') + select('disk.zfs.checksum','Prüfsummen',zfs.checksum,[['on','Standard'],['fletcher4','Fletcher4'],['sha256','SHA-256']]) + select('disk.zfs.compress','Kompression',zfs.compress,[['on','Standard'],['off','Aus'],['lzjb','LZJB'],['lz4','LZ4'],['zle','ZLE'],['gzip','Gzip'],['zstd','Zstandard']]); const storage = select('disk.filesystem','Dateisystem',disks.filesystem,[['zfs','ZFS'],['ext4','ext4 / LVM'],['xfs','XFS / LVM']],{required}) + select('disk.mode','Datenträgerauswahl',diskMode,[['all','Automatisch – Server mit einer Zielplatte'],['filtered','Über Seriennummer oder WWN auswählen']],{required}) + `
Für Server mit genau einer Zielplatte: Es werden alle vom Installer erkannten Zielplatten verwendet. Seriennummer und Gerätename sind nicht erforderlich. Die tatsächliche Anzahl wird nicht geprüft.
` + `
${filtering}
` + input('disk.inventory_evidence','Referenz zur Hardwareprüfung',disks.inventory_evidence,{full:true,hint:'Bei Auswahl per Seriennummer oder WWN erforderlich; bei automatischer Auswahl optional.'}) + `
${zfsFields}
` + `
${lvmNumbers.map(([key,label,min]) => number(`disk.lvm.${key}`,label,lvm[key],min,1000000)).join('')}
`; return `
${override?'

Nicht aktivierte Bereiche und leere Felder übernehmen die veröffentlichten Profilwerte. Gesperrte Profilfelder können nicht überschrieben werden.

':''}
${section('global','Allgemeine Einstellungen',general)} ${section('root_secret_id','Root-Zugang',select('root_secret_id','Gespeichertes Root-Geheimnis',secretId,secrets,{required,full:true,hint:'Root-Zugang vorher unter Einstellungen → Geheimnisse anlegen.'}))} ${section('network','Managementnetz',networking)}${section('disk_setup','Systemdatenträger',storage)}
`; } function read(container, prefix = 'installation', previousValues = {}) { const editor = root(container,prefix), state = JSON.parse(editor.dataset.installationState); const values = clone(object(previousValues,'Installationskonfiguration')); const before = state.values; const control = name => [...editor.querySelectorAll('[name]')].find(node => node.name === `${prefix}.${name}`); const value = name => (control(name)?.value || '').trim(); const enabled = name => !state.override || Boolean(control(`enable.${name}`)?.checked); const assign = (target,key,next) => {if (next === undefined) delete target[key]; else target[key] = next;}; const string = name => value(name) || undefined; const numericValue = (name,label,min,max,integer=false) => { const node = control(name), raw = value(name); if (node?.validity?.badInput) throw new Error(`${label}: Bitte eine gültige Zahl eingeben.`); if (!raw) return undefined; const parsed = Number(raw); if (!Number.isFinite(parsed) || parsed < min || parsed > max || (integer && !Number.isInteger(parsed))) throw new Error(`${label}: ${integer?'Eine ganze Zahl':'Eine Zahl'} zwischen ${min} und ${max} eingeben.`); if (name.startsWith('disk.lvm.') && parsed >= 1000000) throw new Error(`${label}: Die Größe muss kleiner als 1000000 GiB sein.`); return parsed; }; const list = (name,previous) => { const result = readDataEditor(editor,`${prefix}.${name}`); if (!Array.isArray(result) || result.some(item => typeof item !== 'string' || !item.trim())) throw new Error(`${name.includes('ssh')?'SSH-Schlüssel':'Datenträgerkennungen'}: Jeden Eintrag ausfüllen oder den leeren Eintrag entfernen.`); return result.length || Array.isArray(previous) && previous.length === 0 ? result : undefined; }; const base = name => clone(object(Object.hasOwn(values,name)?values[name]:before[name],name)); if (enabled('global')) { const global = base('global'); for (const key of ['keyboard','country','timezone','mailto','fqdn']) assign(global,key,string(`global.${key}`)); const reboot = value('global.reboot-on-error'); if (reboot && !['true','false'].includes(reboot)) throw new Error('Neustart bei Fehler: Bitte Ja, Nein oder Übernehmen wählen.'); assign(global,'reboot-on-error',reboot ? reboot === 'true' : undefined); assign(global,'root-ssh-keys',list('global.root-ssh-keys',before.global?.['root-ssh-keys'])); values.global = global; } else delete values.global; if (enabled('root_secret_id')) assign(values,'root_secret_id',string('root_secret_id')); else delete values.root_secret_id; if (enabled('network')) { const network = base('network'); for (const key of ['source','gateway','dns','cidr']) assign(network,key,string(`network.${key}`)); const filter = Object.create(null); for (const row of editor.querySelectorAll('[data-installation-network-row]')) { const input = suffix => [...row.querySelectorAll('[name]')].find(node => node.name === `${prefix}.${suffix}`)?.value.trim() || ''; const selected = input('interface-key'), key = selected === '__custom' ? input('interface-custom') : selected === '__mac' ? 'ID_NET_NAME_MAC' : selected; let expected = input('interface-value'); if (!expected && selected !== '__custom') continue; if (!key || !expected) throw new Error('Management-Schnittstelle: Merkmal und erwarteten Wert vollständig ausfüllen.'); if (selected === '__mac') { const mac = expected.replace(/[:-]/g,'').toLowerCase(); if (!/^[0-9a-f]{12}$/.test(mac)) throw new Error('Management-Schnittstelle: Eine gültige MAC-Adresse wie 02:00:00:00:00:01 eingeben.'); expected = `enx${mac}`; } if (['__proto__','prototype','constructor'].includes(key) || Object.hasOwn(filter,key)) throw new Error('Management-Schnittstelle: Jedes gültige Merkmal darf nur einmal vorkommen.'); filter[key] = expected; } const keepEmptyFilter = before.network?.filter && Object.keys(before.network.filter).length === 0; if (!state.override && !Object.keys(filter).length) throw new Error('Bitte die MAC-Adresse oder ein anderes Merkmal der Management-Schnittstelle angeben.'); assign(network,'filter',Object.keys(filter).length || keepEmptyFilter ? {...filter} : undefined); values.network = network; } else delete values.network; if (enabled('disk_setup')) { const disks = base('disk_setup'), filesystem = value('disk.filesystem'), mode = value('disk.mode'); if (filesystem && !['zfs','ext4','xfs'].includes(filesystem)) throw new Error('Bitte ZFS, ext4 oder XFS auswählen.'); if (mode && !['all','filtered'].includes(mode)) throw new Error('Bitte eine gültige Datenträgerauswahl treffen.'); assign(disks,'filesystem',filesystem || undefined); assign(disks,'inventory_evidence',string('disk.inventory_evidence')); if (mode === 'all') { const raid = value('disk.zfs.raid'); if ((filesystem && filesystem !== 'zfs') || (raid && raid !== 'raid0') || (!state.override && (!filesystem || !raid))) throw new Error('Die automatische Datenträgerauswahl benötigt ZFS mit RAID0.'); disks.selection = 'all'; for (const key of ['filter','filter_match','expected_count','expected_serials']) delete disks[key]; } else { delete disks.selection; const key = value('disk.filter.key'), pattern = value('disk.filter.value'); if ((key && pattern) && !['ID_SERIAL','ID_SERIAL_SHORT','ID_WWN'].includes(key)) throw new Error('Bitte Seriennummer oder WWN zur Datenträgerauswahl verwenden.'); if (mode === 'filtered' && !state.override && (!key || !pattern)) throw new Error('Bitte ein Datenträgermerkmal und einen Seriennummern- oder WWN-Filter eingeben.'); assign(disks,'filter',key && pattern ? {[key]:pattern} : undefined); assign(disks,'filter_match',string('disk.filter_match')); const serials = list('disk.expected_serials',before.disk_setup?.expected_serials); if (mode === 'filtered' && !state.override && !serials?.length) throw new Error('Bitte mindestens eine bestätigte Datenträgerkennung hinzufügen.'); if (serials && (serials.length > 16 || new Set(serials).size !== serials.length || serials.some(item => /[*?\[\]]/.test(item)))) throw new Error('Datenträgerkennungen: Höchstens 16 unterschiedliche, konkrete Seriennummern oder WWNs eingeben.'); const count = numericValue('disk.expected_count','Anzahl bestätigter Datenträger',1,16,true); if (count !== undefined && serials && count !== serials.length) throw new Error('Die Anzahl muss zur Liste der bestätigten Datenträger passen.'); assign(disks,'expected_serials',serials); assign(disks,'expected_count',count ?? (!state.override && serials?.length ? serials.length : undefined)); if (disks.filter_match && !['all','any'].includes(disks.filter_match)) throw new Error('Bitte eine gültige Filterverknüpfung wählen.'); if (disks.inventory_evidence && disks.inventory_evidence.length < 5) throw new Error('Die Referenz zur Hardwareprüfung muss mindestens fünf Zeichen enthalten.'); } if (filesystem === 'zfs' || !filesystem && (before.disk_setup?.zfs || [...editor.querySelectorAll('[name]')].some(node => node.name.startsWith(`${prefix}.disk.zfs.`) && node.value))) { const zfs = clone(object(disks.zfs,'ZFS')); assign(zfs,'raid',string('disk.zfs.raid')); for (const [key,label,min,max,integer] of zfsNumbers) assign(zfs,key,numericValue(`disk.zfs.${key}`,label,min,max,integer)); for (const key of ['checksum','compress']) assign(zfs,key,string(`disk.zfs.${key}`)); if (zfs.raid && !raids.some(([raid]) => raid === zfs.raid)) throw new Error('Bitte einen unterstützten ZFS-Verbund auswählen.'); if (zfs.checksum && !['on','fletcher4','sha256'].includes(zfs.checksum)) throw new Error('Bitte eine unterstützte ZFS-Prüfsumme auswählen.'); if (zfs.compress && !['on','off','lzjb','lz4','zle','gzip','zstd'].includes(zfs.compress)) throw new Error('Bitte eine unterstützte ZFS-Kompression auswählen.'); const count = disks.expected_serials?.length, minimum = {raid0:1,raid1:2,raid10:4,'raidz-1':3,'raidz-2':4,'raidz-3':5}; if (mode === 'filtered' && count && zfs.raid && (count < minimum[zfs.raid] || zfs.raid === 'raid10' && count % 2)) throw new Error('Die bestätigte Datenträgeranzahl passt nicht zum gewählten ZFS-Verbund.'); disks.zfs = zfs; if (filesystem) delete disks.lvm; } else if (filesystem) delete disks.zfs; if (['ext4','xfs'].includes(filesystem) || !filesystem && (before.disk_setup?.lvm || [...editor.querySelectorAll('[name]')].some(node => node.name.startsWith(`${prefix}.disk.lvm.`) && node.value))) { if (disks.expected_serials?.length > 1) throw new Error('ext4 und XFS benötigen genau einen bestätigten Systemdatenträger.'); const lvm = clone(object(disks.lvm,'LVM')); for (const [key,label,min] of lvmNumbers) assign(lvm,key,numericValue(`disk.lvm.${key}`,label,min,1000000)); if (Object.keys(lvm).length || Object.hasOwn(before.disk_setup || {},'lvm')) disks.lvm = lvm; else delete disks.lvm; } else if (filesystem) delete disks.lvm; values.disk_setup = disks; } else delete values.disk_setup; if (state.override) for (const key of ['global','network','disk_setup']) { if (values[key] && !Object.keys(values[key]).length && (!Object.hasOwn(before,key) || Object.keys(before[key]).length)) delete values[key]; } return values; } function wire(container, prefix = 'installation') { const editor = root(container,prefix), state = JSON.parse(editor.dataset.installationState); if (editor.dataset.installationWired) return; editor.dataset.installationWired = 'true'; wireDataEditors(editor); const control = name => [...editor.querySelectorAll('[name]')].find(node => node.name === `${prefix}.${name}`); const visible = (node,show) => {node.hidden = !show; for (const item of node.querySelectorAll('input,select,textarea,button')) item.disabled = !show;}; function refresh() { for (const section of editor.querySelectorAll('[data-installation-section]')) { const enabled = !state.override || control(`enable.${section.dataset.installationSection}`).checked; visible(section.querySelector('[data-installation-section-body]'),enabled); } const diskEnabled = !state.override || control('enable.disk_setup').checked; const filesystem = control('disk.filesystem').value, mode = control('disk.mode').value; visible(editor.querySelector('[data-installation-filter-fields]'),diskEnabled && mode !== 'all'); visible(editor.querySelector('[data-installation-zfs-fields]'),diskEnabled && (!filesystem || filesystem === 'zfs')); visible(editor.querySelector('[data-installation-lvm-fields]'),diskEnabled && (!filesystem || ['ext4','xfs'].includes(filesystem))); editor.querySelector('[data-installation-all-note]').hidden = !diskEnabled || mode !== 'all'; control('disk.inventory_evidence').required = !state.override && mode === 'filtered'; for (const row of editor.querySelectorAll('[data-installation-network-row]')) { const select = [...row.querySelectorAll('[name]')].find(node => node.name === `${prefix}.interface-key`); const expected = [...row.querySelectorAll('[name]')].find(node => node.name === `${prefix}.interface-value`); expected.placeholder = select.value === '__mac' ? '02:00:00:00:00:01' : 'Erwarteter Merkmalswert'; visible(row.querySelector('[data-installation-custom-key]'),(!state.override || control('enable.network').checked) && select.value === '__custom'); } } editor.addEventListener('change', event => { const name = event.target.name; if (name === `${prefix}.disk.mode` && event.target.value === 'all') { control('disk.filesystem').value = 'zfs'; control('disk.zfs.raid').value = 'raid0'; } if (name === `${prefix}.disk.filesystem` && event.target.value && event.target.value !== 'zfs' && control('disk.mode').value === 'all') control('disk.mode').value = 'filtered'; if (name === `${prefix}.disk.zfs.raid` && event.target.value !== 'raid0' && control('disk.mode').value === 'all') control('disk.mode').value = 'filtered'; refresh(); }); editor.addEventListener('click', event => { const add = event.target.closest('[data-installation-add-interface]'), remove = event.target.closest('[data-installation-remove-interface]'); if (add) editor.querySelector('[data-installation-network-rows]').insertAdjacentHTML('beforeend',networkRow(prefix)); if (remove) remove.closest('[data-installation-network-row]').remove(); if (add || remove) {event.preventDefault(); refresh();} }); refresh(); } return {render,read,wire}; })(); function installationEditor(values = {}, options = {}) { return installationForm.render(values,options); } function readInstallationEditor(container,prefix = 'installation',previousValues = {}) { return installationForm.read(container,prefix,previousValues); } function wireInstallationEditor(container,prefix = 'installation') { installationForm.wire(container,prefix); }