|
|
|
@ -16,6 +16,7 @@ |
|
|
|
<button data-page="ssid">SSID</button> |
|
|
|
<button data-page="network">Network & Server</button> |
|
|
|
<button data-page="io">I/O</button> |
|
|
|
<button data-page="log">Log</button> |
|
|
|
</div> |
|
|
|
</details> |
|
|
|
<details class="nav"> |
|
|
|
@ -60,8 +61,8 @@ |
|
|
|
<div class="file-upload-wrapper-device" |
|
|
|
data-file-type="device" |
|
|
|
data-expected-filename="deviceConfig.json" |
|
|
|
data-upload-url="http://localhost:8080/setting/device" |
|
|
|
data-get-url="http://localhost:8080/setting/get-device"> |
|
|
|
data-upload-url="/setting/device" |
|
|
|
data-get-url="/setting/get-device"> |
|
|
|
<input type="file" class="upload-input-device" accept=".json" style="display: none;"> |
|
|
|
<button class="select-file-btn-device">Load Local Device File</button> |
|
|
|
</div> |
|
|
|
@ -98,17 +99,19 @@ |
|
|
|
id="update_server_port"/></div> |
|
|
|
<div class="grid-3-col"><span>DGPS Server</span> <input id="rtcm_server_ip" placeholder="ex)123.123.123.123"/><input |
|
|
|
id="rtcm_server_port"/></div> |
|
|
|
<div class="grid-3-col"><span>OPC-UA</span> <input id="opc_ua_server_ip" placeholder="ex)123.123.123.123"/><input |
|
|
|
<div class="grid-3-col"><span>OPC_UA</span> <input id="opc_ua_server_ip" placeholder="ex)123.123.123.123"/><input |
|
|
|
id="opc_ua_server_port"/></div> |
|
|
|
<div class="grid-3-col"><span>MODBUS</span> <input id="modbus_server_ip" placeholder="ex)123.123.123.123"/><input |
|
|
|
id="modbus_server_port"/></div> |
|
|
|
<div class="grid-3-col"><span>LTE Server</span> <input id="lte_server_ip" placeholder="ex)123.123.123.123"/><input |
|
|
|
id="lte_server_port"/></div> |
|
|
|
</div> |
|
|
|
<div class="actions"> |
|
|
|
<div class="file-upload-wrapper-device" |
|
|
|
data-file-type="device" |
|
|
|
data-expected-filename="deviceConfig.json" |
|
|
|
data-upload-url="http://localhost:8080/setting/device" |
|
|
|
data-get-url="http://localhost:8080/setting/get-device"> |
|
|
|
data-upload-url="/setting/device" |
|
|
|
data-get-url="/setting/get-device"> |
|
|
|
<input type="file" class="upload-input-device" accept=".json" style="display: none;"> |
|
|
|
<button class="select-file-btn-device">Load Local Device File</button> |
|
|
|
</div> |
|
|
|
@ -169,14 +172,14 @@ |
|
|
|
</select></div> |
|
|
|
<div class="io-row"><span class="label-inline">Parity</span><span |
|
|
|
class="colon">:</span><select id="rs485_parity"> |
|
|
|
<option value="">NONE</option> |
|
|
|
<option value="no">NONE</option> |
|
|
|
<option value="odd">ODD</option> |
|
|
|
<option value="even">EVEN</option> |
|
|
|
</select></div> |
|
|
|
<div class="io-row"><span class="label-inline">Data bits</span><span |
|
|
|
class="colon">:</span><input id="rs485_data_bits"></div> |
|
|
|
class="colon">:</span><input id="rs485_databits"></div> |
|
|
|
<div class="io-row"><span class="label-inline">Stop bits</span><span |
|
|
|
class="colon">:</span><input id="rs485_stop_bits"></div> |
|
|
|
class="colon">:</span><input id="rs485_stopbits"></div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!-- CAN --> |
|
|
|
@ -239,8 +242,38 @@ |
|
|
|
<div class="file-upload-wrapper-device" |
|
|
|
data-file-type="device" |
|
|
|
data-expected-filename="deviceConfig.json" |
|
|
|
data-upload-url="http://localhost:8080/setting/device" |
|
|
|
data-get-url="http://localhost:8080/setting/get-device"> |
|
|
|
data-upload-url="/setting/device" |
|
|
|
data-get-url="/setting/get-device"> |
|
|
|
<input type="file" class="upload-input-device" accept=".json" style="display: none;"> |
|
|
|
<button class="select-file-btn-device">Load Local Device File</button> |
|
|
|
</div> |
|
|
|
<button class="download-device-file">Download Local Device File</button> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
|
|
|
|
<!-- ▽ LOG PAGE ▽ --> |
|
|
|
<section id="page-log" class="page" style="display:none"> |
|
|
|
<div class="card"> |
|
|
|
<h3>Log Configuration</h3> |
|
|
|
<div class="io-group"> |
|
|
|
<div class="io-fieldset"> |
|
|
|
<div class="io-row"><span class="label-inline">Log Save</span><span class="colon">:</span> |
|
|
|
<select id="log_save"> |
|
|
|
<option value="off">OFF</option> |
|
|
|
<option value="on">ON</option> |
|
|
|
</select> |
|
|
|
</div> |
|
|
|
<div class="io-row"><span class="label-inline">Max Size (MB)</span><span class="colon">:</span><input id="log_max_size" type="number" min="1" max="1000"></div> |
|
|
|
<div class="io-row"><span class="label-inline">Max Duration (days)</span><span class="colon">:</span><input id="log_max_duration" type="number" min="1" max="365"></div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="actions"> |
|
|
|
<div class="file-upload-wrapper-device" |
|
|
|
data-file-type="device" |
|
|
|
data-expected-filename="deviceConfig.json" |
|
|
|
data-upload-url="/setting/device" |
|
|
|
data-get-url="/setting/get-device"> |
|
|
|
<input type="file" class="upload-input-device" accept=".json" style="display: none;"> |
|
|
|
<button class="select-file-btn-device">Load Local Device File</button> |
|
|
|
</div> |
|
|
|
@ -388,8 +421,8 @@ |
|
|
|
<div class="file-upload-wrapper-protocol" |
|
|
|
data-file-type="protocol" |
|
|
|
data-expected-filename="protocolConfig.json" |
|
|
|
data-upload-url="http://localhost:8080/setting/protocol" |
|
|
|
data-get-url="http://localhost:8080/setting/get-protocol"> |
|
|
|
data-upload-url="/setting/protocol" |
|
|
|
data-get-url="/setting/get-protocol"> |
|
|
|
<input type="file" class="upload-input-protocol" accept=".json" style="display: none;"> |
|
|
|
<button class="select-file-btn-protocol">Load Local Protocol File</button> |
|
|
|
</div> |
|
|
|
@ -476,8 +509,8 @@ |
|
|
|
<div class="file-upload-wrapper-protocol" |
|
|
|
data-file-type="protocol" |
|
|
|
data-expected-filename="protocolConfig.json" |
|
|
|
data-upload-url="http://localhost:8080/setting/protocol" |
|
|
|
data-get-url="http://localhost:8080/setting/get-protocol"> |
|
|
|
data-upload-url="/setting/protocol" |
|
|
|
data-get-url="/setting/get-protocol"> |
|
|
|
<input type="file" class="upload-input-protocol" accept=".json" style="display: none;"> |
|
|
|
<button class="select-file-btn-protocol">Load Local Protocol File</button> |
|
|
|
</div> |
|
|
|
@ -557,8 +590,8 @@ |
|
|
|
<div class="file-upload-wrapper-protocol" |
|
|
|
data-file-type="protocol" |
|
|
|
data-expected-filename="protocolConfig.json" |
|
|
|
data-upload-url="http://localhost:8080/setting/protocol" |
|
|
|
data-get-url="http://localhost:8080/setting/get-protocol"> |
|
|
|
data-upload-url="/setting/protocol" |
|
|
|
data-get-url="/setting/get-protocol"> |
|
|
|
<input type="file" class="upload-input-protocol" accept=".json" style="display: none;"> |
|
|
|
<button class="select-file-btn-protocol">Load Local Protocol File</button> |
|
|
|
</div> |
|
|
|
@ -631,12 +664,12 @@ |
|
|
|
<div class="file-upload-wrapper-protocol" |
|
|
|
data-file-type="protocol" |
|
|
|
data-expected-filename="protocolConfig.json" |
|
|
|
data-upload-url="http://localhost:8080/setting/protocol" |
|
|
|
data-get-url="http://localhost:8080/setting/get-protocol"> |
|
|
|
data-upload-url="/setting/protocol" |
|
|
|
data-get-url="/setting/get-protocol"> |
|
|
|
<input type="file" class="upload-input-protocol" accept=".json" style="display: none;"> |
|
|
|
<button class="select-file-btn-protocol">Load Local Protocol File</button> |
|
|
|
</div> |
|
|
|
<button class="download-protocol-file">Download Device File</button> |
|
|
|
<button class="download-protocol-file">Download Protocol File</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
</section> |
|
|
|
@ -648,6 +681,9 @@ |
|
|
|
|
|
|
|
<!-- ───── Script ───── --> |
|
|
|
<script> |
|
|
|
const API_BASE = ''; // same-origin (Nginx 프록시 사용) |
|
|
|
const api = (p) => `${API_BASE}${p.startsWith('/') ? '' : '/'}${p}`; |
|
|
|
|
|
|
|
const ssidList = document.getElementById('ssid-list'); |
|
|
|
const ssidTmpl = document.getElementById('ssid-template'); |
|
|
|
const data = { |
|
|
|
@ -667,11 +703,13 @@ |
|
|
|
opc_ua_server_port: null, |
|
|
|
modbus_server_ip: '', |
|
|
|
modbus_server_port: null, |
|
|
|
lte_server_ip: '', |
|
|
|
lte_server_port: null, |
|
|
|
|
|
|
|
/* CAN / RS-485 */ |
|
|
|
can_bus_type: '', can_baudrate: null, |
|
|
|
rs485_mode: '', rs485_baudrate: null, rs485_data_bits: null, |
|
|
|
rs485_parity: '', rs485_stop_bits: null, |
|
|
|
rs485_mode: '', rs485_baudrate: null, rs485_databits: null, |
|
|
|
rs485_parity: '', rs485_stopbits: null, |
|
|
|
|
|
|
|
/* IMU (축 + 부호) */ |
|
|
|
imu_remap_x: '', imu_remap_y: '', imu_remap_z: '', |
|
|
|
@ -679,8 +717,12 @@ |
|
|
|
|
|
|
|
/* LTE 설정 */ |
|
|
|
lte_ip: '', lte_netmask: '', lte_gateway: '', lte_port: null, |
|
|
|
|
|
|
|
/* Log 설정 */ |
|
|
|
log_save: 'off', log_max_size: null, log_max_duration: null, |
|
|
|
|
|
|
|
/* SSID 목록 */ |
|
|
|
WIFI_SSID: [{wifi_ssid: '', wifi_password: '', wifi_security: 'none'}], |
|
|
|
WIFI_SSID: [{wifi_ssid: '', wifi_passwd: '', wifi_security: 'none'}], |
|
|
|
}; |
|
|
|
|
|
|
|
const configIdMap = { |
|
|
|
@ -694,28 +736,72 @@ |
|
|
|
'di2': 'di2', |
|
|
|
'di3': 'di3', |
|
|
|
'reg_can_input': 'can_input', |
|
|
|
'reg_device_type': 'device_type', |
|
|
|
'reg_device_type': 'dev_type', |
|
|
|
'reg_dr_on_off': 'dr_on', |
|
|
|
'reg_equipment': 'equipment', |
|
|
|
'reg_two_byte_order': 'two_byte', |
|
|
|
'reg_four_byte_order': 'four_byte', |
|
|
|
'reg_two_byte_order': 'two_byte_order', |
|
|
|
'reg_four_byte_order': 'four_byte_order', |
|
|
|
'reg_protocol': 'protocol', |
|
|
|
'reg_speed_data_source': 'speed_data_source', |
|
|
|
'reg_speed_data_source': 'speed_data', |
|
|
|
'reg_version': 'version', |
|
|
|
'reg_equipment_id': 'equipment_id', |
|
|
|
'reg_meid': 'meid', |
|
|
|
'reg_meid': 'MEID', |
|
|
|
'reg_heading_on': 'heading_on', |
|
|
|
'reg_heading_imu_on': 'heading_imu_on', |
|
|
|
'reg_fixmode_on': 'fixmode_on' |
|
|
|
'reg_fixmode_on': 'fix_mode_on' |
|
|
|
}; |
|
|
|
|
|
|
|
let lastLoadedRegisterData = {}; |
|
|
|
|
|
|
|
// 하이픈/공백 → 언더스코어, 대문자 통일 |
|
|
|
const normProtocol = (p) => String(p ?? '').trim().replace(/[-\s]+/g, '_'); |
|
|
|
|
|
|
|
// OPC UA 배열을 'OPC_UA' 또는 'OPC-UA' 어느 쪽이 와도 읽게 |
|
|
|
const getOpcArr = (o) => (o?.['OPC_UA'] ?? o?.['OPC-UA'] ?? []); |
|
|
|
|
|
|
|
function normalizeRegisterForPost(reg) { |
|
|
|
const out = JSON.parse(JSON.stringify(reg || {})); // 얕은 복제 방지 |
|
|
|
if (out.protocol) { |
|
|
|
out.protocol = String(out.protocol).trim().toUpperCase().replace(/[-\s]+/g, '_'); |
|
|
|
} |
|
|
|
if (out.can_input) { |
|
|
|
out.can_input = String(out.can_input).trim().toUpperCase(); // 'on'/'off' → 'ON'/'OFF' |
|
|
|
} |
|
|
|
|
|
|
|
// MEID: 숫자만이면 number, 그 외는 string |
|
|
|
if (out.MEID !== undefined && out.MEID !== null) { |
|
|
|
const m = String(out.MEID).trim(); |
|
|
|
out.MEID = /^\d+$/.test(m) ? Number(m) : m; |
|
|
|
} |
|
|
|
|
|
|
|
// enum 정규화 추가 |
|
|
|
const up = s => String(s ?? '').trim().toUpperCase(); |
|
|
|
const up_us = s => up(s).replace(/[-\s]+/g, '_'); |
|
|
|
|
|
|
|
// 상위 enum들도 혹시 소문자면 대문자로 |
|
|
|
['dev_type','equipment','speed_data'].forEach(k=>{ |
|
|
|
if (out[k]) out[k] = up_us(out[k]); |
|
|
|
}); |
|
|
|
|
|
|
|
// 빈 문자열은 전부 제거(서버 enum null 허용 대비) |
|
|
|
Object.keys(out).forEach(k => { |
|
|
|
if (out[k] === '') delete out[k]; |
|
|
|
}); |
|
|
|
|
|
|
|
// 업로드 파일이 'OPC-UA' 키를 쓰는 경우를 위해 보정 |
|
|
|
if (out['OPC-UA'] && !out['OPC_UA']) { |
|
|
|
out['OPC_UA'] = out['OPC-UA']; |
|
|
|
delete out['OPC-UA']; |
|
|
|
} |
|
|
|
return out; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const pages = { |
|
|
|
ssid: document.getElementById('page-ssid'), |
|
|
|
network: document.getElementById('page-network'), |
|
|
|
io: document.getElementById('page-io'), |
|
|
|
log: document.getElementById('page-log'), |
|
|
|
reg: document.getElementById('page-reg'), |
|
|
|
modbus: document.getElementById('page-modbus'), |
|
|
|
opcua: document.getElementById('page-opcua'), |
|
|
|
@ -777,8 +863,6 @@ |
|
|
|
const jsonData = JSON.parse(DeviceFileContent); |
|
|
|
|
|
|
|
loadFromData(jsonData); |
|
|
|
|
|
|
|
console.log(`Loaded ${DeviceFileType} data from local file`, jsonData); |
|
|
|
} catch (error) { |
|
|
|
console.error(`Error uploading file (${DeviceFileType}):`, error); |
|
|
|
alert(`Error uploading file: ${error.message} (${DeviceFileType})`); |
|
|
|
@ -830,6 +914,7 @@ |
|
|
|
try { |
|
|
|
const fileContent = e.target.result; |
|
|
|
const jsonData = JSON.parse(fileContent); |
|
|
|
const payload = normalizeRegisterForPost(jsonData); |
|
|
|
|
|
|
|
// 업로드 유효성 검사 로직 |
|
|
|
if (!isValidRegisterConfig(jsonData)){ // 예를 들어, isValidModbusConfig(jsonData) 등 |
|
|
|
@ -837,9 +922,7 @@ |
|
|
|
uploadFileInput.value = ''; |
|
|
|
return; |
|
|
|
} |
|
|
|
loadFromRegisterJson(jsonData,fileType); |
|
|
|
console.log(`Loaded ${fileType} data from local file (not saved).`, jsonData); |
|
|
|
|
|
|
|
loadFromRegisterJson(jsonData); |
|
|
|
} catch (error) { |
|
|
|
alert(`Error uploading file: ${error.message} (${fileType})`); |
|
|
|
uploadFileInput.value = ''; |
|
|
|
@ -854,14 +937,16 @@ |
|
|
|
|
|
|
|
/*---device 다운로드 버튼에 대한 로직---*/ |
|
|
|
document.querySelectorAll('.download-device-file').forEach(btn=>{btn.addEventListener('click', () => { |
|
|
|
downloadDeviceJson(collectDeviceData(data)); |
|
|
|
downloadDeviceJson(collectDeviceData()); |
|
|
|
})}) |
|
|
|
|
|
|
|
/*---protocol 다운로드 버튼에 대한 로직---*/ |
|
|
|
document.querySelectorAll('.download-protocol-file').forEach(btn=>{btn.addEventListener('click', () => { |
|
|
|
downloadProtocolJson(collectProtocolData(data)); |
|
|
|
downloadProtocolJson(collectProtocolData()); |
|
|
|
})}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('reg_can_input')?.addEventListener('change', toggleProtocolMenus); |
|
|
|
|
|
|
|
/*---modbus 테이블에 행 추가 로직---*/ |
|
|
|
@ -942,7 +1027,7 @@ |
|
|
|
const canRowTemplate = document.getElementById('can-row-template'); |
|
|
|
if (canRowTemplate) { |
|
|
|
const newRow = canRowTemplate.content.cloneNode(true); |
|
|
|
opcUaList.appendChild(newRow); |
|
|
|
canFieldList.appendChild(newRow); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -1175,23 +1260,23 @@ |
|
|
|
function saveToConfig() { |
|
|
|
const deviceData = collectDeviceData(); |
|
|
|
const protocolData = collectProtocolData(); |
|
|
|
|
|
|
|
if (!isValidRegisterConfig(protocolData)) { |
|
|
|
const normalizedProtocol = normalizeRegisterForPost(protocolData); |
|
|
|
if (!isValidRegisterConfig(normalizedProtocol)) { |
|
|
|
alert("Register settings are currently invalid. Cannot be saved."); |
|
|
|
return; |
|
|
|
} |
|
|
|
//device |
|
|
|
const deviceReq = fetch('http://localhost:8080/setting/device', { |
|
|
|
const deviceReq = fetch('/setting/device', { |
|
|
|
method: 'POST', |
|
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
|
body: JSON.stringify(deviceData) |
|
|
|
}); |
|
|
|
|
|
|
|
//protocol |
|
|
|
const protocolReq = fetch('http://localhost:8080/setting/protocol', { |
|
|
|
const protocolReq = fetch('/setting/protocol', { |
|
|
|
method: 'POST', |
|
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
|
body: JSON.stringify(protocolData) |
|
|
|
body: JSON.stringify(normalizedProtocol) |
|
|
|
}); |
|
|
|
|
|
|
|
// 모든 요청이 성공하면 성공 알림 |
|
|
|
@ -1275,7 +1360,7 @@ |
|
|
|
const el = document.getElementById(k); |
|
|
|
if (el) { |
|
|
|
const val = el.value.trim(); |
|
|
|
if (['can_baudrate', 'rs485_baudrate'].includes(k)) { |
|
|
|
if (['can_baudrate', 'rs485_baudrate', 'log_max_size', 'log_max_duration'].includes(k)) { |
|
|
|
res[k] = val === '' ? null : Number(val); |
|
|
|
} else { |
|
|
|
res[k] = val; |
|
|
|
@ -1358,7 +1443,7 @@ |
|
|
|
|
|
|
|
// OPC_UA 테이블 수집 (빈 값은 필드에서 제외) |
|
|
|
const opcuaRows = document.querySelectorAll('#opcua-field-list tr'); |
|
|
|
collectedRegister.OPC_UA = []; |
|
|
|
collectedRegister["OPC_UA"] = []; |
|
|
|
opcuaRows.forEach(row => { |
|
|
|
const field = row.querySelector('.opcua-field-display')?.textContent.trim(); |
|
|
|
if (field) { |
|
|
|
@ -1464,40 +1549,48 @@ |
|
|
|
|
|
|
|
/*-------페이지 로드시 서버에 있는 데이터를 불러오는 함수---------*/ |
|
|
|
async function loadInitialData() { |
|
|
|
console.log('Starting loadInitialData...'); |
|
|
|
|
|
|
|
// 서버에서 파일 목록 조회 및 로드 (Device) |
|
|
|
try { |
|
|
|
const deviceResponse = await fetch('http://localhost:8080/setting/get-device'); |
|
|
|
if (deviceResponse.ok) { |
|
|
|
console.log('Fetching device config...'); |
|
|
|
const deviceResponse = await fetch('/setting/get-device'); |
|
|
|
console.log('Device response status:', deviceResponse.status); |
|
|
|
|
|
|
|
if (deviceResponse.status === 204) { |
|
|
|
console.log('No device config in DB yet. (skip)'); |
|
|
|
} else if (deviceResponse.ok) { |
|
|
|
const deviceData = await deviceResponse.json(); |
|
|
|
if (deviceData) { |
|
|
|
loadFromData(deviceData); |
|
|
|
console.log('Device Load completed (Default file: deviceConfig.json)'); |
|
|
|
} else { |
|
|
|
console.warn('Device response received, but no body found.'); |
|
|
|
} |
|
|
|
console.log('Device data received:', deviceData); |
|
|
|
loadFromData(deviceData); |
|
|
|
console.log('Device Load completed (from DB)'); |
|
|
|
} else { |
|
|
|
console.warn(`Device load failed: HTTP ${deviceResponse.status}`); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('Device load failed:', error.message); |
|
|
|
console.error('Device load failed:', error.message, error); |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
const protocolResponse = await fetch('http://localhost:8080/setting/get-protocol'); |
|
|
|
if (protocolResponse.ok) { |
|
|
|
console.log('Fetching protocol config...'); |
|
|
|
const protocolResponse = await fetch('/setting/get-protocol'); |
|
|
|
console.log('Protocol response status:', protocolResponse.status); |
|
|
|
|
|
|
|
if (protocolResponse.status === 204) { |
|
|
|
console.log('No protocol config in DB yet. (skip)'); |
|
|
|
} else if (protocolResponse.ok) { |
|
|
|
const protocolData = await protocolResponse.json(); |
|
|
|
if (protocolData) { |
|
|
|
loadFromRegisterJson(protocolData); |
|
|
|
console.log('Protocol Load completed (Default file: protocolConfig.json)'); |
|
|
|
} else { |
|
|
|
console.warn('Protocol response received, but no body found.'); |
|
|
|
} |
|
|
|
console.log('Protocol data received:', protocolData); |
|
|
|
loadFromRegisterJson(protocolData); |
|
|
|
console.log('Protocol Load completed (from DB)'); |
|
|
|
} else { |
|
|
|
console.warn(`Protocol load failed: HTTP ${protocolResponse.status}`); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('./Protocol load failed:', error.message); |
|
|
|
console.error('Protocol load failed:', error.message, error); |
|
|
|
} |
|
|
|
|
|
|
|
console.log('loadInitialData completed'); |
|
|
|
} |
|
|
|
/*-----------------------------------------------------*/ |
|
|
|
|
|
|
|
@ -1505,7 +1598,7 @@ |
|
|
|
function createSsidRow(obj = {wifi_ssid: '', wifi_password: '', wifi_security: 'none'}) { |
|
|
|
const node = ssidTmpl.content.firstElementChild.cloneNode(true); |
|
|
|
node.querySelector('.ssid-name').value = obj.wifi_ssid ?? ''; |
|
|
|
node.querySelector('.ssid-pw').value = obj.wifi_password ?? ''; |
|
|
|
node.querySelector('.ssid-pw').value = obj.wifi_passwd ?? ''; |
|
|
|
node.querySelector('select').value = (obj.wifi_security ?? 'none').toLowerCase(); |
|
|
|
|
|
|
|
const deleteBtn = document.createElement('button'); |
|
|
|
@ -1549,7 +1642,7 @@ |
|
|
|
const pw = row.querySelector('.ssid-pw').value.trim(); |
|
|
|
const sec = row.querySelector('select').value; |
|
|
|
if (ssid === '' && pw === '') return; |
|
|
|
if (ssid) arr.push({wifi_ssid: ssid, wifi_password: pw, wifi_security: sec}); |
|
|
|
if (ssid) arr.push({wifi_ssid: ssid, wifi_passwd: pw, wifi_security: sec}); |
|
|
|
}); |
|
|
|
return arr |
|
|
|
} |
|
|
|
@ -1562,24 +1655,27 @@ |
|
|
|
console.error("Validation Error: Data is not an object or is null."); |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (typeof data.protocol === 'string') { |
|
|
|
data.protocol = normProtocol(data.protocol); |
|
|
|
} |
|
|
|
|
|
|
|
// RegisterDTO의 필수 필드 (백엔드 snake_case와 일치시키세요) |
|
|
|
const topLevelRequiredFields = [ |
|
|
|
'protocol', |
|
|
|
'analog_input_level', |
|
|
|
'can_input', |
|
|
|
'device_type', |
|
|
|
'dev_type', |
|
|
|
'dr_on', |
|
|
|
'equipment', |
|
|
|
'two_byte', |
|
|
|
'four_byte', |
|
|
|
'speed_data_source', |
|
|
|
'two_byte_order', |
|
|
|
'four_byte_order', |
|
|
|
'speed_data', |
|
|
|
'version', |
|
|
|
'equipment_id', |
|
|
|
'meid', |
|
|
|
'MEID', |
|
|
|
'heading_on', |
|
|
|
'heading_imu_on', |
|
|
|
'fixmode_on' |
|
|
|
'fix_mode_on' |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
@ -1592,7 +1688,6 @@ |
|
|
|
} |
|
|
|
|
|
|
|
// 3. 각 필드의 값 타입 및 유효성 검증 |
|
|
|
|
|
|
|
// protocol 필드 유효성 검사 |
|
|
|
const validProtocols = ['MODBUS', 'OPC_UA', 'CAN', 'NONE']; |
|
|
|
if (typeof data.protocol !== 'string' || !validProtocols.includes(data.protocol)) { |
|
|
|
@ -1600,24 +1695,13 @@ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// ON/OFF 상태를 나타내는 필드 검사 |
|
|
|
const boolLikeFields = ['dr_on', 'heading_on', 'heading_imu_on', 'fixmode_on']; |
|
|
|
for (const field of boolLikeFields) { |
|
|
|
if (typeof data[field] !== 'string' || (data[field] !== 'on' && data[field] !== 'off')) { |
|
|
|
console.error(`Validation Error: Invalid value for boolean-like field '${field}'. Expected 'ON' or 'OFF', got '${data[field]}'`); |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 기타 최상위 필드의 타입 검사 (예시, 필요에 따라 추가) |
|
|
|
if (typeof data.analog_input_level !== 'string' && typeof data.analog_input_level !== 'number') { |
|
|
|
console.error("Validation Error: 'analog_input_level' is not a string or number."); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 리스트 내부 필드 유효성 검사 |
|
|
|
|
|
|
|
// MODBUS 리스트 검증 |
|
|
|
if (data.protocol === 'MODBUS') { |
|
|
|
// protocol이 'MODBUS'일 경우, MODBUS 배열은 필수 |
|
|
|
@ -1655,8 +1739,8 @@ |
|
|
|
|
|
|
|
// OPC_UA 리스트 검증 |
|
|
|
if (data.protocol === 'OPC_UA') { |
|
|
|
// protocol이 'OPC_UA'일 경우, OPC_UA 배열은 필수 |
|
|
|
if (!data.hasOwnProperty('OPC_UA') || !Array.isArray(data.OPC_UA)) { |
|
|
|
const arr = getOpcArr(data); |
|
|
|
if (!Array.isArray(arr)) { |
|
|
|
console.error("Validation Error: 'OPC_UA' array is required and must be an array when protocol is 'OPC_UA'."); |
|
|
|
return false; |
|
|
|
} |
|
|
|
@ -1675,11 +1759,6 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
// protocol이 'OPC_UA'가 아닌 경우, OPC_UA 배열은 존재하지 않거나 빈 배열이어야 함 |
|
|
|
if (data.hasOwnProperty('OPC_UA') && data.OPC_UA.length > 0) { |
|
|
|
console.error("Validation Error: 'OPC_UA' array must be empty or absent when protocol is not 'OPC_UA'."); |
|
|
|
return false; |
|
|
|
} |
|
|
|
// 만약 존재한다면 배열 타입이어야 함 |
|
|
|
if (data.hasOwnProperty('OPC_UA') && !Array.isArray(data.OPC_UA)) { |
|
|
|
console.error("Validation Error: 'OPC_UA' field must be an array if present, even when protocol is not 'OPC_UA'."); |
|
|
|
@ -1687,7 +1766,6 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// CAN 리스트 검증 (can_input이 'on'인 경우 필수 - protocol 값과는 독립적) |
|
|
|
if (data.can_input.toLowerCase() === 'on') { |
|
|
|
if (!data.hasOwnProperty('CAN') || !Array.isArray(data.CAN)) { |
|
|
|
@ -1754,8 +1832,15 @@ |
|
|
|
|
|
|
|
/*--------초기 랜더링 화면 함수------------*/ |
|
|
|
function loadFromData(obj) { |
|
|
|
console.log('loadFromData called with:', obj); |
|
|
|
|
|
|
|
/* Wi-Fi 모드 radio */ |
|
|
|
document.querySelector(`input[name="ipMode"][value="${obj.wifi_static === 'on' ? 'static' : 'dhcp'}"]`).checked = true; |
|
|
|
const radioElement = document.querySelector(`input[name="ipMode"][value="${obj.wifi_static === 'on' ? 'static' : 'dhcp'}"]`); |
|
|
|
if (radioElement) { |
|
|
|
radioElement.checked = true; |
|
|
|
} else { |
|
|
|
console.warn('Radio element not found for wifi_static:', obj.wifi_static); |
|
|
|
} |
|
|
|
|
|
|
|
/* 일반 input/select */ |
|
|
|
for (const k in obj) { |
|
|
|
@ -1851,7 +1936,9 @@ |
|
|
|
|
|
|
|
// protocol 값이 있으면 select 값을 변경 (데이터 로드 시에만) |
|
|
|
if (register.protocol && regProtocolSelect) { |
|
|
|
regProtocolSelect.value = register.protocol; |
|
|
|
const p = normProtocol(register.protocol); // 'OPC-UA' → 'OPC_UA' |
|
|
|
regProtocolSelect.value = p; |
|
|
|
lastLoadedRegisterData.protocol = p; |
|
|
|
} |
|
|
|
|
|
|
|
// 프로토콜 값에 따라 사이드바 버튼 토글 |
|
|
|
@ -1888,8 +1975,9 @@ |
|
|
|
const opcUaRowTemplate = document.getElementById('opcua-row-template'); |
|
|
|
if (opcUaFieldList && opcUaRowTemplate) { |
|
|
|
opcUaFieldList.innerHTML = ''; |
|
|
|
if (Array.isArray(register.OPC_UA) && register.OPC_UA.length > 0) { |
|
|
|
register.OPC_UA.forEach(field => { |
|
|
|
const opcArr = getOpcArr(register); |
|
|
|
if (Array.isArray(opcArr) && opcArr.length > 0) { |
|
|
|
opcArr.forEach(field => { |
|
|
|
const newRow = opcUaRowTemplate.content.cloneNode(true); |
|
|
|
newRow.querySelector('.opcua-field-display').textContent = field.field ?? ''; |
|
|
|
newRow.querySelector('.opcua-addr-display').textContent = field.addr ?? ''; |
|
|
|
|