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);
}
-
}