/** * ethernet.js — Wired interface IP configuration (eth0 + WAN uplink) * * v1.5.0 Phase 2: io.js의 두 wired 카드를 분리. * v1.7.1 Task 3: the second card was mislabeled "LTE Interface" — the device has * no cellular modem; this is a wired uplink port. The visible label/icon are * corrected to WAN/wired. The DB keys stay `lte_*` (backend/Java contract). * server endpoints (TIOT/Update/RTCM/uplink 등)는 server-setting.js 소유. * * Data model (nested — matches io.js state.device.eth / .lte convention): * state.device.eth = { ip, netmask, gateway } * state.device.lte = { ip, netmask, gateway, port } ← lte_* = wired WAN keys * * eth_ip / eth_netmask / eth_gateway / lte_ip / lte_netmask / lte_gateway are * present as DOM element IDs (ip-input component convention) and also used as * string literals for test detection. */ import { state, bindIpGroup } from '../state.js'; import { escapeHtml } from '../utils.js'; import { isValidIP } from '../validator.js'; import { renderIpOctets, setupIpOctets, collectIpValue } from '../components/ip-input.js'; import { icon } from '../icons.js'; // Field definitions — flat DOM ids map to nested state paths const ETH_FIELDS = [ { key: 'eth_ip', label: 'IP Address', group: 'eth', prop: 'ip' }, { key: 'eth_netmask', label: 'Subnet Mask', group: 'eth', prop: 'netmask' }, { key: 'eth_gateway', label: 'Gateway', group: 'eth', prop: 'gateway' }, ]; const LTE_FIELDS = [ { key: 'lte_ip', label: 'IP Address', group: 'lte', prop: 'ip' }, { key: 'lte_netmask', label: 'Subnet Mask', group: 'lte', prop: 'netmask' }, { key: 'lte_gateway', label: 'Gateway', group: 'lte', prop: 'gateway' }, ]; const ethernetPage = { render(container) { const d = state.device || {}; const eth = d.eth || {}; const lte = d.lte || {}; container.innerHTML = `

${icon('cable', { size: 18 })} Management Ethernet (eth1)

${ETH_FIELDS.map(f => `
${renderIpOctets(f.key, eth[f.prop] || '')}
`).join('')}

${icon('cable', { size: 18 })} Uplink Ethernet (eth0)

Physical eth0 uplink. Endpoint ports are configured on Server Setting.

${LTE_FIELDS.map(f => `
${renderIpOctets(f.key, lte[f.prop] || '')}
`).join('')}
`; }, mount(container) { setupIpOctets(container); // Ethernet IP bindings ETH_FIELDS.forEach(({ key, group, prop }) => { bindIpGroup(container, key, () => state.device?.[group]?.[prop] || '', (val) => { if (!state.device) state.device = {}; if (!state.device[group]) state.device[group] = {}; state.device[group][prop] = val; state.isDirty = true; } ); }); // LTE IP bindings LTE_FIELDS.forEach(({ key, group, prop }) => { bindIpGroup(container, key, () => state.device?.[group]?.[prop] || '', (val) => { if (!state.device) state.device = {}; if (!state.device[group]) state.device[group] = {}; state.device[group][prop] = val; state.isDirty = true; } ); }); // Register data collector window.__pageCollectors.ethernet = collectEthernetData; }, destroy() {}, validate() { const errors = []; const d = state.device || {}; ETH_FIELDS.forEach(({ key, group, prop, label }) => { const val = d[group]?.[prop]; if (val && val !== '...' && !isValidIP(val)) { errors.push({ field: key, page: 'ethernet', message: `${label} — Invalid IP format` }); } }); LTE_FIELDS.forEach(({ key, group, prop, label }) => { const val = d[group]?.[prop]; if (val && val !== '...' && !isValidIP(val)) { errors.push({ field: key, page: 'ethernet', message: `${label} — Invalid IP format` }); } }); return errors; }, }; function collectEthernetData() { if (!state.device) state.device = {}; state.device.eth = { ip: collectIpValue('eth_ip'), netmask: collectIpValue('eth_netmask'), gateway: collectIpValue('eth_gateway'), }; const prevLte = state.device.lte || {}; state.device.lte = { ...prevLte, ip: collectIpValue('lte_ip'), netmask: collectIpValue('lte_netmask'), gateway: collectIpValue('lte_gateway'), }; } export default ethernetPage;