From d0ff28e492629474eac2a561baf186e939bce55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9B=90=ED=98=95?= <147372294+blissyou@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:51:55 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/index.html | 649 +++++++++++++-------------- 1 file changed, 324 insertions(+), 325 deletions(-) diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 464ffa0..ec059f9 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -723,14 +723,24 @@ can: document.getElementById('page-can') }; + /*----DOM 인터페이스 초기화----*/ document.addEventListener('DOMContentLoaded', () => { + const opcUaList = document.getElementById('opcua-field-list'); + const modbusList = document.getElementById('modbus-field-list'); + const fileUploadWrappersDevice = document.querySelectorAll('.file-upload-wrapper-device'); + const modbusAddBtn = document.getElementById('modbus_add_btn'); + const modbusFieldList = document.getElementById('modbus-field-list'); + const modbusRowTemplate = document.getElementById('modbus-row-template'); + const opcUaAddBtn = document.getElementById('opcua_add_btn'); + const opcUaFieldList = document.getElementById('opcua-field-list'); + const opcUaRowTemplate = document.getElementById('opcua-row-template'); + const canFieldList = document.getElementById('can-field-list'); + // 페이지 로드 시 자동으로 JSON 파일들 로드 loadInitialData(); // Device 파일 업로드 // 모든 파일 업로드 래퍼 섹션을 가져옵니다. - const fileUploadWrappersDevice = document.querySelectorAll('.file-upload-wrapper-device'); - fileUploadWrappersDevice.forEach(wrapper => { // 현재 래퍼 내에서 input[type="file"]과 버튼을 찾습니다. const uploadDeviceFileInput = wrapper.querySelector('.upload-input-device'); @@ -868,13 +878,14 @@ // 파일 선택 시 (change 이벤트 발생) 바로 업로드 로직 시작 uploadFileInput.addEventListener('change', async (event) => { + const selectedFile = event.target.files[0]; + const fileName = selectedFile.name; + const reader = new FileReader(); + if (event.target.files.length === 0) { return; } - const selectedFile = event.target.files[0]; - const fileName = selectedFile.name; - // 파일명 검증 (data-expected-filename 사용) if (fileName !== expectedFileName){ alert(`Invalid file. '${expectedFileName}' Please select a file. Current file: ${fileName} `); @@ -882,23 +893,11 @@ return; } - const reader = new FileReader(); - reader.onload = async (e) => { try { const fileContent = e.target.result; const jsonData = JSON.parse(fileContent); - // JSON 내용 유효성 검증 - // 모든 파일이 Register 설정이라면 isValidRegisterConfig를 그대로 사용 - // 만약 파일 타입에 따라 다른 유효성 검사가 필요하다면, - // 여기에 조건부 로직을 추가해야 합니다. - if (!isValidRegisterConfig(jsonData)){ // 예를 들어, isValidModbusConfig(jsonData) 등 - alert(`The contents of the selected file are not in a valid ${fileType} setting file format.`); - uploadFileInput.value = ''; - return; - } - // 백엔드 저장 API 호출 (data-upload-url 사용) const response = await fetch(uploadUrl, { method: 'POST', @@ -908,20 +907,24 @@ body: JSON.stringify(jsonData) }); + const result = await response.json(); + // 업로드 유효성 검사 로직 + if (!isValidRegisterConfig(jsonData)){ // 예를 들어, isValidModbusConfig(jsonData) 등 + alert(`The contents of the selected file are not in a valid ${fileType} setting file format.`); + uploadFileInput.value = ''; + return; + } + if (!response.ok) { const errorText = await response.text(); throw new Error(`File upload failed: ${response.status} ${response.statusText} - ${errorText}`); } - const result = await response.json(); - if (result.result && result.result.result_code === 200) { alert(`File uploaded to Device successfully (${fileType})`); console.log(`Upload successful for ${fileType}:`, result); // 업로드 성공 후 최신 데이터를 Device에서 다시 불러와 UI 업데이트 (data-get-url 사용) - // loadFromRegisterJson은 Register 데이터만 처리하는 함수이므로, - // 만약 Modbus/OPCUA/CAN 데이터 로드 함수가 별도로 있다면 여기서 조건부 호출 필요 fetch(getUrl) .then(res => res.json()) .then(apiResponseData => { @@ -954,187 +957,26 @@ alert(`An error occurred while reading the file. (${fileType})`); uploadFileInput.value = ''; }; - reader.readAsText(selectedFile); }); }); - function isValidRegisterConfig(data) { - // 1. data가 객체이고 null이 아닌지 확인 - if (typeof data !== 'object' || data === null) { - console.error("Validation Error: Data is not an object or is null."); - return false; - } - - // RegisterDTO의 필수 필드 (백엔드 snake_case와 일치시키세요) - const topLevelRequiredFields = [ - 'protocol', - 'analog_input_level', - 'can_input', - 'device_type', - 'dr_on', - 'equipment', - 'two_byte', - 'four_byte', - 'speed_data_source', - 'version', - 'equipment_id', - 'meid', - 'heading_on', - 'heading_imu_on', - 'fixmode_on' - - ]; - - // 2. 모든 최상위 필수 필드가 존재하는지 확인 - for (const field of topLevelRequiredFields) { - if (!data.hasOwnProperty(field)) { - console.error(`Validation Error: Missing required top-level field: '${field}'`); - return false; - } - } - - // 3. 각 필드의 값 타입 및 유효성 검증 - - // protocol 필드 유효성 검사 - const validProtocols = ['MODBUS', 'OPC_UA', 'CAN', 'NONE']; - if (typeof data.protocol !== 'string' || !validProtocols.includes(data.protocol)) { - console.error(`Validation Error: Invalid or missing 'protocol' field. Expected one of ${validProtocols.join(', ')}, got '${data.protocol}'`); - 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 배열은 필수 - if (!data.hasOwnProperty('MODBUS') || !Array.isArray(data.MODBUS)) { - console.error("Validation Error: 'MODBUS' array is required and must be an array when protocol is 'MODBUS'."); - return false; - } - // MODBUS 배열 내용 유효성 검사 - const modbusRequiredFields = ['field', 'idt', 'addr', 'odt', 'dv']; - for (const [index, item] of data.MODBUS.entries()) { - if (typeof item !== 'object' || item === null) { - console.error(`Validation Error: MODBUS item at index ${index} is not an object.`); - return false; - } - for (const field of modbusRequiredFields) { - if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') { - console.error(`Validation Error: MODBUS item at index ${index} missing or empty required field: '${field}'`); - return false; - } - } - } - } else { - // protocol이 'MODBUS'가 아닌 경우, MODBUS 배열은 존재하지 않거나 빈 배열이어야 함 - if (data.hasOwnProperty('MODBUS') && data.MODBUS.length > 0) { - console.error("Validation Error: 'MODBUS' array must be empty or absent when protocol is not 'MODBUS'."); - return false; - } - // 만약 존재한다면 배열 타입이어야 함 - if (data.hasOwnProperty('MODBUS') && !Array.isArray(data.MODBUS)) { - console.error("Validation Error: 'MODBUS' field must be an array if present, even when protocol is not 'MODBUS'."); - return false; - } - } - + /*---저장 버튼에 대한 로직---*/ + document.querySelectorAll('.save-file').forEach(btn=>btn.addEventListener('click', () => {saveToConfig()})) - // OPC_UA 리스트 검증 - if (data.protocol === 'OPC_UA') { - // protocol이 'OPC_UA'일 경우, OPC_UA 배열은 필수 - if (!data.hasOwnProperty('OPC_UA') || !Array.isArray(data.OPC_UA)) { - console.error("Validation Error: 'OPC_UA' array is required and must be an array when protocol is 'OPC_UA'."); - return false; - } - // OPC_UA 배열 내용 유효성 검사 - const opcUaRequiredFields = ['field', 'ns', 'addr', 'odt', 'dv']; - for (const [index, item] of data.OPC_UA.entries()) { - if (typeof item !== 'object' || item === null) { - console.error(`Validation Error: OPC_UA item at index ${index} is not an object.`); - return false; - } - for (const field of opcUaRequiredFields) { - if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') { - console.error(`Validation Error: OPC_UA item at index ${index} missing or empty required field: '${field}'`); - return false; - } - } - } - } 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'."); - return false; - } - } + /*---device 다운로드 버튼에 대한 로직---*/ + document.querySelectorAll('.download-device-file').forEach(btn=>{btn.addEventListener('click', () => { + downloadDeviceJson(collectDeviceData(data)); + })}) + /*---protocol 다운로드 버튼에 대한 로직---*/ + document.querySelectorAll('.download-protocol-file').forEach(btn=>{btn.addEventListener('click', () => { + downloadProtocolJson(collectProtocolData(data)); + })}) - // CAN 리스트 검증 (can_input이 'on'인 경우 필수 - protocol 값과는 독립적) - if (data.can_input.toLowerCase() === 'on') { - if (!data.hasOwnProperty('CAN') || !Array.isArray(data.CAN)) { - console.error("Validation Error: 'CAN' is required when can_input is 'on', and must be an array."); - return false; - } - const canRequiredFields = ['field', 'id', 'odt', 'dv']; - for (const [index, item] of data.CAN.entries()) { - if (typeof item !== 'object' || item === null) { - console.error(`Validation Error: CAN item at index ${index} is not an object.`); - return false; - } - for (const field of canRequiredFields) { - if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') { - console.error(`Validation Error: CAN item at index ${index} missing or empty required field: '${field}'`); - return false; - } - } - } - } else { - // can_input이 'off'일 경우 CAN이 존재하면 배열이어야 함 (선택 사항) - if (data.hasOwnProperty('CAN') && data.CAN.length > 0) { - console.error("Validation Error: 'CAN' array must be empty or absent when can_input is 'off'."); - return false; - } - if (data.hasOwnProperty('CAN') && !Array.isArray(data.CAN)) { - console.error("Validation Error: 'CAN' field should be an array if present, even when can_input is 'off'."); - return false; - } - } + document.getElementById('reg_can_input')?.addEventListener('change', toggleProtocolMenus); - // 모든 검사를 통과하면 유효함 - return true; - } - const list = document.getElementById('modbus-field-list'); - // 처음에 행이 없으면 빈 행을 하나 추가 - if (list.children.length === 0) { - const modbusRowTemplate = document.getElementById('modbus-row-template'); - if (modbusRowTemplate) { - const newRow = modbusRowTemplate.content.cloneNode(true); - list.appendChild(newRow); - } - } - let selectedRow = null; + /*---modbus 테이블에 행 추가 로직---*/ document.getElementById('modbus-field-list').addEventListener('click', function (e) { let tr = e.target.closest('tr'); if (!tr) return; @@ -1152,16 +994,18 @@ document.getElementById('modbus_expr_input').value = tr.querySelector('.expr-display').textContent; document.getElementById('modbus_mask_input').value = tr.querySelector('.mask-display').textContent; }); - - const opcUalist = document.getElementById('opcua-field-list'); // 처음에 행이 없으면 빈 행을 하나 추가 - if (opcUalist.children.length === 0) { - const opcUaRowTemplate = document.getElementById('opcua-row-template'); - if (opcUaRowTemplate) { - const newRow = opcUaRowTemplate.content.cloneNode(true); - opcUalist.appendChild(newRow); + if (modbusList.children.length === 0) { + const modbusRowTemplate = document.getElementById('modbus-row-template'); + if (modbusRowTemplate) { + const newRow = modbusRowTemplate.content.cloneNode(true); + modbusList.appendChild(newRow); } } + let selectedRow = null; + /*-------------------------------------------*/ + + /*---opcua 테이블에 행 추가 로직---*/ document.getElementById('opcua-field-list').addEventListener('click', function (e) { let tr = e.target.closest('tr'); if (!tr) return; @@ -1179,44 +1023,47 @@ document.getElementById('opcua_expr_input').value = tr.querySelector('.opcua-expr-display').textContent; document.getElementById('opcua_mask_input').value = tr.querySelector('.opcua-mask-display').textContent; }); + // 처음에 행이 없으면 빈 행을 하나 추가 + if (opcUaList.children.length === 0) { + const opcUaRowTemplate = document.getElementById('opcua-row-template'); + if (opcUaRowTemplate) { + const newRow = opcUaRowTemplate.content.cloneNode(true); + opcUaList.appendChild(newRow); + } + } - const modbusAddBtn = document.getElementById('modbus_add_btn'); - const modbusFieldList = document.getElementById('modbus-field-list'); - const modbusRowTemplate = document.getElementById('modbus-row-template'); - - if (modbusAddBtn && modbusFieldList && modbusRowTemplate) { - modbusAddBtn.addEventListener('click', () => { - const field = document.getElementById('modbus_field_input').value; - const addr = document.getElementById('modbus_addr_input').value; - const idt = document.getElementById('modbus_idt_input').value; - const odt = document.getElementById('modbus_odt_input').value; - const defaultValue = document.getElementById('modbus_dv_input').value; - const shift = document.getElementById('modbus_shift_input').value; - const expr = document.getElementById('modbus_expr_input').value; - const mask = document.getElementById('modbus_mask_input').value; - - const newRow = modbusRowTemplate.content.cloneNode(true); - newRow.querySelector('.field-display').textContent = field; - newRow.querySelector('.addr-display').textContent = addr; - newRow.querySelector('.idt-display').textContent = idt; - newRow.querySelector('.odt-display').textContent = odt; - newRow.querySelector('.dv-display').textContent = defaultValue; - newRow.querySelector('.shift-display').textContent = shift; - newRow.querySelector('.expr-display').textContent = expr; - newRow.querySelector('.mask-display').textContent = mask; - - modbusFieldList.appendChild(newRow); + modbusAddBtn.addEventListener('click', () => { + const field = document.getElementById('modbus_field_input').value; + const addr = document.getElementById('modbus_addr_input').value; + const idt = document.getElementById('modbus_idt_input').value; + const odt = document.getElementById('modbus_odt_input').value; + const defaultValue = document.getElementById('modbus_dv_input').value; + const shift = document.getElementById('modbus_shift_input').value; + const expr = document.getElementById('modbus_expr_input').value; + const mask = document.getElementById('modbus_mask_input').value; + + const newRow = modbusRowTemplate.content.cloneNode(true); + newRow.querySelector('.field-display').textContent = field; + newRow.querySelector('.addr-display').textContent = addr; + newRow.querySelector('.idt-display').textContent = idt; + newRow.querySelector('.odt-display').textContent = odt; + newRow.querySelector('.dv-display').textContent = defaultValue; + newRow.querySelector('.shift-display').textContent = shift; + newRow.querySelector('.expr-display').textContent = expr; + newRow.querySelector('.mask-display').textContent = mask; + + modbusFieldList.appendChild(newRow); + + document.getElementById('modbus_field_input').value = ''; + document.getElementById('modbus_addr_input').value = ''; + document.getElementById('modbus_idt_input').value = ''; + document.getElementById('modbus_odt_input').value = ''; + document.getElementById('modbus_dv_input').value = ''; + document.getElementById('modbus_shift_input').value = ''; + document.getElementById('modbus_expr_input').value = ''; + document.getElementById('modbus_mask_input').value = ''; + }); - document.getElementById('modbus_field_input').value = ''; - document.getElementById('modbus_addr_input').value = ''; - document.getElementById('modbus_idt_input').value = ''; - document.getElementById('modbus_odt_input').value = ''; - document.getElementById('modbus_dv_input').value = ''; - document.getElementById('modbus_shift_input').value = ''; - document.getElementById('modbus_expr_input').value = ''; - document.getElementById('modbus_mask_input').value = ''; - }); - } document.getElementById('modbus_modify_btn').onclick = function () { if (!selectedRow) return alert('Please select the row you want to modify'); @@ -1239,43 +1086,38 @@ document.querySelectorAll('.modbus-input-area input').forEach(i => i.value = ''); }; - const opcUaAddBtn = document.getElementById('opcua_add_btn'); - const opcUaFieldList = document.getElementById('opcua-field-list'); - const opcUaRowTemplate = document.getElementById('opcua-row-template'); - - if (opcUaAddBtn && opcUaFieldList && opcUaRowTemplate) { - opcUaAddBtn.addEventListener('click', () => { - const field = document.getElementById('opcua_field_input').value; - const addr = document.getElementById('opcua_addr_input').value; - const ns = document.getElementById('opcua_ns_input').value; - const odt = document.getElementById('opcua_odt_input').value; - const defaultValue = document.getElementById('opcua_dv_input').value; - const shift = document.getElementById('opcua_shift_input').value; - const expr = document.getElementById('opcua_expr_input').value; - const mask = document.getElementById('opcua_mask_input').value; - - const newRow = opcUaRowTemplate.content.cloneNode(true); - newRow.querySelector('.opcua-field-display').textContent = field; - newRow.querySelector('.opcua-addr-display').textContent = addr; - newRow.querySelector('.opcua-ns-display').textContent = ns; - newRow.querySelector('.opcua-odt-display').textContent = odt; - newRow.querySelector('.opcua-dv-display').textContent = defaultValue; - newRow.querySelector('.opcua-shift-display').textContent = shift; - newRow.querySelector('.opcua-expr-display').textContent = expr; - newRow.querySelector('.opcua-mask-display').textContent = mask; - - opcUaFieldList.appendChild(newRow); + opcUaAddBtn.addEventListener('click', () => { + const field = document.getElementById('opcua_field_input').value; + const addr = document.getElementById('opcua_addr_input').value; + const ns = document.getElementById('opcua_ns_input').value; + const odt = document.getElementById('opcua_odt_input').value; + const defaultValue = document.getElementById('opcua_dv_input').value; + const shift = document.getElementById('opcua_shift_input').value; + const expr = document.getElementById('opcua_expr_input').value; + const mask = document.getElementById('opcua_mask_input').value; + + const newRow = opcUaRowTemplate.content.cloneNode(true); + newRow.querySelector('.opcua-field-display').textContent = field; + newRow.querySelector('.opcua-addr-display').textContent = addr; + newRow.querySelector('.opcua-ns-display').textContent = ns; + newRow.querySelector('.opcua-odt-display').textContent = odt; + newRow.querySelector('.opcua-dv-display').textContent = defaultValue; + newRow.querySelector('.opcua-shift-display').textContent = shift; + newRow.querySelector('.opcua-expr-display').textContent = expr; + newRow.querySelector('.opcua-mask-display').textContent = mask; + + opcUaFieldList.appendChild(newRow); + + document.getElementById('opcua_field_input').value = ''; + document.getElementById('opcua_addr_input').value = ''; + document.getElementById('opcua_ns_input').value = ''; + document.getElementById('opcua_odt_input').value = ''; + document.getElementById('opcua_dv_input').value = ''; + document.getElementById('opcua_shift_input').value = ''; + document.getElementById('opcua_expr_input').value = ''; + document.getElementById('opcua_mask_input').value = ''; + }); - document.getElementById('opcua_field_input').value = ''; - document.getElementById('opcua_addr_input').value = ''; - document.getElementById('opcua_ns_input').value = ''; - document.getElementById('opcua_odt_input').value = ''; - document.getElementById('opcua_dv_input').value = ''; - document.getElementById('opcua_shift_input').value = ''; - document.getElementById('opcua_expr_input').value = ''; - document.getElementById('opcua_mask_input').value = ''; - }); - } document.getElementById('opcua_modify_btn').onclick = function () { if (!selectedRow) return alert('Please select the row you want to modify'); @@ -1298,7 +1140,15 @@ document.querySelectorAll('.opcua-input-area input').forEach(i => i.value = ''); }; - // CAN 테이블 + if (canFieldList.children.length === 0) { + const canRowTemplate = document.getElementById('can-row-template'); + if (canRowTemplate) { + const newRow = canRowTemplate.content.cloneNode(true); + canFieldList.appendChild(newRow); + } + } + + // CAN 테이블 추가 로직 document.getElementById('can_add_btn').addEventListener('click', () => { const field = document.getElementById('can_field_input').value; const id = document.getElementById('can_id_input').value; @@ -1325,6 +1175,8 @@ document.getElementById('can_expr_input').value = ''; document.getElementById('can_mask_input').value = ''; }); + + // can 테이블 수정 로직 document.getElementById('can_modify_btn').onclick = function () { if (!selectedRow) return alert('Please select the row you want to modify'); selectedRow.querySelector('.can-field-display').textContent = document.getElementById('can_field_input').value; @@ -1335,6 +1187,8 @@ selectedRow.querySelector('.can-expr-display').textContent = document.getElementById('can_expr_input').value; selectedRow.querySelector('.can-mask-display').textContent = document.getElementById('can_mask_input').value; }; + + //can 테이블 삭제 로직 document.getElementById('can_delete_btn').onclick = function () { if (!selectedRow) return alert('Select the row you want to delete'); const list = document.getElementById('can-field-list'); @@ -1343,7 +1197,7 @@ document.querySelectorAll('.can-input-area input').forEach(i => i.value = ''); }; - + // 프로토콜에 따른 사이드바 변경 로직 regProtocolSelect = document.getElementById('reg_protocol'); if (regProtocolSelect) { regProtocolSelect.addEventListener('change', () => { @@ -1353,6 +1207,7 @@ toggleProtocolMenus(); } + /*----서버로 데이터 전송하는 함수----*/ function saveToConfig() { const deviceData = collectDeviceData(); const protocolData = collectProtocolData(); @@ -1361,14 +1216,14 @@ alert("Register settings are currently invalid. Cannot be saved."); return; } - - // 두 개의 fetch를 병렬 실행 + //device const deviceReq = fetch('http://localhost:8080/setting/device', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(deviceData) }); + //protocol const protocolReq = fetch('http://localhost:8080/setting/protocol', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -1390,56 +1245,19 @@ alert('save failure'); }); } - - - - document.querySelectorAll('.save-file').forEach(btn=>btn.addEventListener('click', () => {saveToConfig()})) - - - document.querySelectorAll('.download-device-file').forEach(btn=>{btn.addEventListener('click', () => { - downloadDeviceJson(collectDeviceData(data)); - })}) - - document.querySelectorAll('.download-protocol-file').forEach(btn=>{btn.addEventListener('click', () => { - downloadProtocolJson(collectProtocolData(data)); - })}) - - - document.getElementById('reg_can_input')?.addEventListener('change', toggleProtocolMenus); - - const canFieldList = document.getElementById('can-field-list'); - if (canFieldList.children.length === 0) { - const canRowTemplate = document.getElementById('can-row-template'); - if (canRowTemplate) { - const newRow = canRowTemplate.content.cloneNode(true); - canFieldList.appendChild(newRow); - } - } - // CAN row 클릭 시 선택 표시 및 입력폼에 값 반영 - canFieldList.addEventListener('click', function (e) { - let tr = e.target.closest('tr'); - if (!tr) return; - Array.from(this.children).forEach(row => row.classList.remove('selected-row')); - tr.classList.add('selected-row'); - selectedRow = tr; - document.getElementById('can_field_input').value = tr.querySelector('.can-field-display').textContent; - document.getElementById('can_id_input').value = tr.querySelector('.can-id-display').textContent; - document.getElementById('can_odt_input').value = tr.querySelector('.can-odt-display').textContent; - document.getElementById('can_dv_input').value = tr.querySelector('.can-dv-display').textContent; - document.getElementById('can_shift_input').value = tr.querySelector('.can-shift-display').textContent; - document.getElementById('can_expr_input').value = tr.querySelector('.can-expr-display').textContent; - document.getElementById('can_mask_input').value = tr.querySelector('.can-mask-display').textContent; - }); }); + //네이게이션 바 클릭시 해당 페이지로 이동 로직 document.querySelectorAll('.nav-items button').forEach(btn => { btn.addEventListener('click', () => { showPage(btn.dataset.page); }); }); + //ssid 행 추가 버튼 document.getElementById('add-ssid').addEventListener('click', addSsidRow); + //ssid 행 삭제 버튼 document.getElementById('delete-ssid').addEventListener('click', () => { if (ssidList.children.length > 1) { ssidList.removeChild(ssidList.lastElementChild); @@ -1451,7 +1269,6 @@ } }) - /*-----device input데이터 받는 함수------*/ function collectDeviceData() { const res = { ...data }; @@ -1536,6 +1353,7 @@ collectedRegister.MODBUS.push(obj); } }); + // OPC_UA 테이블 수집 (빈 값은 필드에서 제외) const opcuaRows = document.querySelectorAll('#opcua-field-list tr'); collectedRegister.OPC_UA = []; @@ -1561,6 +1379,7 @@ collectedRegister.OPC_UA.push(obj); } }); + // CAN 테이블 수집 (빈 값은 필드에서 제외) const canRows = document.querySelectorAll('#can-field-list tr'); collectedRegister.CAN = []; @@ -1670,6 +1489,7 @@ } /*-----------------------------------------------------*/ + /*------데이터상 ssid 추가 함수--------*/ 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 ?? ''; @@ -1677,7 +1497,9 @@ node.querySelector('select').value = (obj.wifi_security ?? 'none').toLowerCase(); return node; } + /*-----------------------------------------------------*/ + /*-------ssid 데이터 리스트 추가 함수-----------*/ function addSsidRow() { ssidList.appendChild(createSsidRow()); } @@ -1694,7 +1516,9 @@ } } }); + /*-----------------------------------------------------*/ + /*-------ssid 리스트 병합 함수-------------*/ function gatherSsidRows() { const arr = []; ssidList.querySelectorAll('.ssid-row').forEach(row => { @@ -1705,8 +1529,180 @@ }); return arr.length ? arr : [{wifi_ssid: '', wifi_password: '', wifi_security: 'none'}]; } + /*-----------------------------------------------------*/ + + /*------프로토콜 데이터 유효성 검사 로직---------*/ + function isValidRegisterConfig(data) { + // 1. data가 객체이고 null이 아닌지 확인 + if (typeof data !== 'object' || data === null) { + console.error("Validation Error: Data is not an object or is null."); + return false; + } + + // RegisterDTO의 필수 필드 (백엔드 snake_case와 일치시키세요) + const topLevelRequiredFields = [ + 'protocol', + 'analog_input_level', + 'can_input', + 'device_type', + 'dr_on', + 'equipment', + 'two_byte', + 'four_byte', + 'speed_data_source', + 'version', + 'equipment_id', + 'meid', + 'heading_on', + 'heading_imu_on', + 'fixmode_on' + + ]; + + // 2. 모든 최상위 필수 필드가 존재하는지 확인 + for (const field of topLevelRequiredFields) { + if (!data.hasOwnProperty(field)) { + console.error(`Validation Error: Missing required top-level field: '${field}'`); + return false; + } + } + + // 3. 각 필드의 값 타입 및 유효성 검증 + + // protocol 필드 유효성 검사 + const validProtocols = ['MODBUS', 'OPC_UA', 'CAN', 'NONE']; + if (typeof data.protocol !== 'string' || !validProtocols.includes(data.protocol)) { + console.error(`Validation Error: Invalid or missing 'protocol' field. Expected one of ${validProtocols.join(', ')}, got '${data.protocol}'`); + 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 배열은 필수 + if (!data.hasOwnProperty('MODBUS') || !Array.isArray(data.MODBUS)) { + console.error("Validation Error: 'MODBUS' array is required and must be an array when protocol is 'MODBUS'."); + return false; + } + // MODBUS 배열 내용 유효성 검사 + const modbusRequiredFields = ['field', 'idt', 'addr', 'odt', 'dv']; + for (const [index, item] of data.MODBUS.entries()) { + if (typeof item !== 'object' || item === null) { + console.error(`Validation Error: MODBUS item at index ${index} is not an object.`); + return false; + } + for (const field of modbusRequiredFields) { + if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') { + console.error(`Validation Error: MODBUS item at index ${index} missing or empty required field: '${field}'`); + return false; + } + } + } + } else { + // protocol이 'MODBUS'가 아닌 경우, MODBUS 배열은 존재하지 않거나 빈 배열이어야 함 + if (data.hasOwnProperty('MODBUS') && data.MODBUS.length > 0) { + console.error("Validation Error: 'MODBUS' array must be empty or absent when protocol is not 'MODBUS'."); + return false; + } + // 만약 존재한다면 배열 타입이어야 함 + if (data.hasOwnProperty('MODBUS') && !Array.isArray(data.MODBUS)) { + console.error("Validation Error: 'MODBUS' field must be an array if present, even when protocol is not 'MODBUS'."); + return false; + } + } + + + // OPC_UA 리스트 검증 + if (data.protocol === 'OPC_UA') { + // protocol이 'OPC_UA'일 경우, OPC_UA 배열은 필수 + if (!data.hasOwnProperty('OPC_UA') || !Array.isArray(data.OPC_UA)) { + console.error("Validation Error: 'OPC_UA' array is required and must be an array when protocol is 'OPC_UA'."); + return false; + } + // OPC_UA 배열 내용 유효성 검사 + const opcUaRequiredFields = ['field', 'ns', 'addr', 'odt', 'dv']; + for (const [index, item] of data.OPC_UA.entries()) { + if (typeof item !== 'object' || item === null) { + console.error(`Validation Error: OPC_UA item at index ${index} is not an object.`); + return false; + } + for (const field of opcUaRequiredFields) { + if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') { + console.error(`Validation Error: OPC_UA item at index ${index} missing or empty required field: '${field}'`); + return false; + } + } + } + } 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'."); + return false; + } + } + + + // CAN 리스트 검증 (can_input이 'on'인 경우 필수 - protocol 값과는 독립적) + if (data.can_input.toLowerCase() === 'on') { + if (!data.hasOwnProperty('CAN') || !Array.isArray(data.CAN)) { + console.error("Validation Error: 'CAN' is required when can_input is 'on', and must be an array."); + return false; + } + const canRequiredFields = ['field', 'id', 'odt', 'dv']; + for (const [index, item] of data.CAN.entries()) { + if (typeof item !== 'object' || item === null) { + console.error(`Validation Error: CAN item at index ${index} is not an object.`); + return false; + } + for (const field of canRequiredFields) { + if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') { + console.error(`Validation Error: CAN item at index ${index} missing or empty required field: '${field}'`); + return false; + } + } + } + } else { + // can_input이 'off'일 경우 CAN이 존재하면 배열이어야 함 (선택 사항) + if (data.hasOwnProperty('CAN') && data.CAN.length > 0) { + console.error("Validation Error: 'CAN' array must be empty or absent when can_input is 'off'."); + return false; + } + if (data.hasOwnProperty('CAN') && !Array.isArray(data.CAN)) { + console.error("Validation Error: 'CAN' field should be an array if present, even when can_input is 'off'."); + return false; + } + } + + // 모든 검사를 통과하면 유효함 + return true; + } + /*-----------------------------------------------------*/ + + /*-------network dhcp 이면 input field 잠금------------*/ + document.querySelectorAll('input[name="ipMode"]').forEach(r => r.addEventListener('change', toggleNetInputs)); - /* ───────────── 4. DHCP ↔ Static 전환 입력 잠금 ───────────── */ const netFields = ['wifi_ip', 'wifi_netmask', 'wifi_gateway', 'wifi_dns1', 'wifi_dns2'] .map(id => document.getElementById(id)); @@ -1717,10 +1713,9 @@ el.classList.toggle('disabled', !isStatic); }); } + /*-----------------------------------------------------*/ - document.querySelectorAll('input[name="ipMode"]').forEach(r => r.addEventListener('change', toggleNetInputs)); - - + /*-------imu-remap 정수와 부호 분리 함수-------------*/ function axisValueToPair(val) { return val.startsWith('-') ? {axis: val.slice(1), sign: 'minus'} @@ -1730,8 +1725,9 @@ function pairToAxisValue(axis, sign) { return sign === 'minus' ? `-${axis}` : axis; } + /*-----------------------------------------------------*/ - /* ───────────── 6. 데이터 → 화면 로딩 ───────────── */ + /*--------초기 랜더링 화면 함수------------*/ function loadFromData(obj) { /* Wi-Fi 모드 radio */ document.querySelector(`input[name="ipMode"][value="${obj.wifi_static === 'on' ? 'static' : 'dhcp'}"]`).checked = true; @@ -1756,9 +1752,11 @@ toggleNetInputs(); // 초기 잠금 상태 } - loadFromData(data); + /*-----------------------------------------------------*/ + + /*--------토굴 프로토콜 and can input 함수------------*/ let regProtocolSelect; function toggleProtocolMenus() { @@ -1784,7 +1782,9 @@ const protocolDetails = document.querySelectorAll('details.nav')[1]; if (protocolDetails) protocolDetails.open = true; } + /*-----------------------------------------------------*/ + /*--------서버 DTO 기준 json 데이터 불러오는 함수------------------*/ function loadFromRegisterJson(register) { // register.json 데이터 로드 if (!register) { console.error("Register data is null or undefined."); @@ -1903,7 +1903,6 @@ const newRow = canRowTemplate.content.cloneNode(true); canFieldList.appendChild(newRow); } - }