|
|
@ -723,14 +723,24 @@ |
|
|
can: document.getElementById('page-can') |
|
|
can: document.getElementById('page-can') |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/*----DOM 인터페이스 초기화----*/ |
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
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 파일들 로드 |
|
|
// 페이지 로드 시 자동으로 JSON 파일들 로드 |
|
|
loadInitialData(); |
|
|
loadInitialData(); |
|
|
|
|
|
|
|
|
// Device 파일 업로드 |
|
|
// Device 파일 업로드 |
|
|
// 모든 파일 업로드 래퍼 섹션을 가져옵니다. |
|
|
// 모든 파일 업로드 래퍼 섹션을 가져옵니다. |
|
|
const fileUploadWrappersDevice = document.querySelectorAll('.file-upload-wrapper-device'); |
|
|
|
|
|
|
|
|
|
|
|
fileUploadWrappersDevice.forEach(wrapper => { |
|
|
fileUploadWrappersDevice.forEach(wrapper => { |
|
|
// 현재 래퍼 내에서 input[type="file"]과 버튼을 찾습니다. |
|
|
// 현재 래퍼 내에서 input[type="file"]과 버튼을 찾습니다. |
|
|
const uploadDeviceFileInput = wrapper.querySelector('.upload-input-device'); |
|
|
const uploadDeviceFileInput = wrapper.querySelector('.upload-input-device'); |
|
|
@ -868,13 +878,14 @@ |
|
|
|
|
|
|
|
|
// 파일 선택 시 (change 이벤트 발생) 바로 업로드 로직 시작 |
|
|
// 파일 선택 시 (change 이벤트 발생) 바로 업로드 로직 시작 |
|
|
uploadFileInput.addEventListener('change', async (event) => { |
|
|
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) { |
|
|
if (event.target.files.length === 0) { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const selectedFile = event.target.files[0]; |
|
|
|
|
|
const fileName = selectedFile.name; |
|
|
|
|
|
|
|
|
|
|
|
// 파일명 검증 (data-expected-filename 사용) |
|
|
// 파일명 검증 (data-expected-filename 사용) |
|
|
if (fileName !== expectedFileName){ |
|
|
if (fileName !== expectedFileName){ |
|
|
alert(`Invalid file. '${expectedFileName}' Please select a file. Current file: ${fileName} `); |
|
|
alert(`Invalid file. '${expectedFileName}' Please select a file. Current file: ${fileName} `); |
|
|
@ -882,23 +893,11 @@ |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const reader = new FileReader(); |
|
|
|
|
|
|
|
|
|
|
|
reader.onload = async (e) => { |
|
|
reader.onload = async (e) => { |
|
|
try { |
|
|
try { |
|
|
const fileContent = e.target.result; |
|
|
const fileContent = e.target.result; |
|
|
const jsonData = JSON.parse(fileContent); |
|
|
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 사용) |
|
|
// 백엔드 저장 API 호출 (data-upload-url 사용) |
|
|
const response = await fetch(uploadUrl, { |
|
|
const response = await fetch(uploadUrl, { |
|
|
method: 'POST', |
|
|
method: 'POST', |
|
|
@ -908,20 +907,24 @@ |
|
|
body: JSON.stringify(jsonData) |
|
|
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) { |
|
|
if (!response.ok) { |
|
|
const errorText = await response.text(); |
|
|
const errorText = await response.text(); |
|
|
throw new Error(`File upload failed: ${response.status} ${response.statusText} - ${errorText}`); |
|
|
throw new Error(`File upload failed: ${response.status} ${response.statusText} - ${errorText}`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const result = await response.json(); |
|
|
|
|
|
|
|
|
|
|
|
if (result.result && result.result.result_code === 200) { |
|
|
if (result.result && result.result.result_code === 200) { |
|
|
alert(`File uploaded to Device successfully (${fileType})`); |
|
|
alert(`File uploaded to Device successfully (${fileType})`); |
|
|
console.log(`Upload successful for ${fileType}:`, result); |
|
|
console.log(`Upload successful for ${fileType}:`, result); |
|
|
|
|
|
|
|
|
// 업로드 성공 후 최신 데이터를 Device에서 다시 불러와 UI 업데이트 (data-get-url 사용) |
|
|
// 업로드 성공 후 최신 데이터를 Device에서 다시 불러와 UI 업데이트 (data-get-url 사용) |
|
|
// loadFromRegisterJson은 Register 데이터만 처리하는 함수이므로, |
|
|
|
|
|
// 만약 Modbus/OPCUA/CAN 데이터 로드 함수가 별도로 있다면 여기서 조건부 호출 필요 |
|
|
|
|
|
fetch(getUrl) |
|
|
fetch(getUrl) |
|
|
.then(res => res.json()) |
|
|
.then(res => res.json()) |
|
|
.then(apiResponseData => { |
|
|
.then(apiResponseData => { |
|
|
@ -954,187 +957,26 @@ |
|
|
alert(`An error occurred while reading the file. (${fileType})`); |
|
|
alert(`An error occurred while reading the file. (${fileType})`); |
|
|
uploadFileInput.value = ''; |
|
|
uploadFileInput.value = ''; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
reader.readAsText(selectedFile); |
|
|
reader.readAsText(selectedFile); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
function isValidRegisterConfig(data) { |
|
|
/*---저장 버튼에 대한 로직---*/ |
|
|
// 1. data가 객체이고 null이 아닌지 확인 |
|
|
document.querySelectorAll('.save-file').forEach(btn=>btn.addEventListener('click', () => {saveToConfig()})) |
|
|
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 리스트 검증 |
|
|
/*---device 다운로드 버튼에 대한 로직---*/ |
|
|
if (data.protocol === 'OPC_UA') { |
|
|
document.querySelectorAll('.download-device-file').forEach(btn=>{btn.addEventListener('click', () => { |
|
|
// protocol이 'OPC_UA'일 경우, OPC_UA 배열은 필수 |
|
|
downloadDeviceJson(collectDeviceData(data)); |
|
|
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; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*---protocol 다운로드 버튼에 대한 로직---*/ |
|
|
|
|
|
document.querySelectorAll('.download-protocol-file').forEach(btn=>{btn.addEventListener('click', () => { |
|
|
|
|
|
downloadProtocolJson(collectProtocolData(data)); |
|
|
|
|
|
})}) |
|
|
|
|
|
|
|
|
// CAN 리스트 검증 (can_input이 'on'인 경우 필수 - protocol 값과는 독립적) |
|
|
document.getElementById('reg_can_input')?.addEventListener('change', toggleProtocolMenus); |
|
|
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; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 모든 검사를 통과하면 유효함 |
|
|
/*---modbus 테이블에 행 추가 로직---*/ |
|
|
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; |
|
|
|
|
|
document.getElementById('modbus-field-list').addEventListener('click', function (e) { |
|
|
document.getElementById('modbus-field-list').addEventListener('click', function (e) { |
|
|
let tr = e.target.closest('tr'); |
|
|
let tr = e.target.closest('tr'); |
|
|
if (!tr) return; |
|
|
if (!tr) return; |
|
|
@ -1152,16 +994,18 @@ |
|
|
document.getElementById('modbus_expr_input').value = tr.querySelector('.expr-display').textContent; |
|
|
document.getElementById('modbus_expr_input').value = tr.querySelector('.expr-display').textContent; |
|
|
document.getElementById('modbus_mask_input').value = tr.querySelector('.mask-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) { |
|
|
if (modbusList.children.length === 0) { |
|
|
const opcUaRowTemplate = document.getElementById('opcua-row-template'); |
|
|
const modbusRowTemplate = document.getElementById('modbus-row-template'); |
|
|
if (opcUaRowTemplate) { |
|
|
if (modbusRowTemplate) { |
|
|
const newRow = opcUaRowTemplate.content.cloneNode(true); |
|
|
const newRow = modbusRowTemplate.content.cloneNode(true); |
|
|
opcUalist.appendChild(newRow); |
|
|
modbusList.appendChild(newRow); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
let selectedRow = null; |
|
|
|
|
|
/*-------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
/*---opcua 테이블에 행 추가 로직---*/ |
|
|
document.getElementById('opcua-field-list').addEventListener('click', function (e) { |
|
|
document.getElementById('opcua-field-list').addEventListener('click', function (e) { |
|
|
let tr = e.target.closest('tr'); |
|
|
let tr = e.target.closest('tr'); |
|
|
if (!tr) return; |
|
|
if (!tr) return; |
|
|
@ -1179,44 +1023,47 @@ |
|
|
document.getElementById('opcua_expr_input').value = tr.querySelector('.opcua-expr-display').textContent; |
|
|
document.getElementById('opcua_expr_input').value = tr.querySelector('.opcua-expr-display').textContent; |
|
|
document.getElementById('opcua_mask_input').value = tr.querySelector('.opcua-mask-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'); |
|
|
modbusAddBtn.addEventListener('click', () => { |
|
|
const modbusFieldList = document.getElementById('modbus-field-list'); |
|
|
const field = document.getElementById('modbus_field_input').value; |
|
|
const modbusRowTemplate = document.getElementById('modbus-row-template'); |
|
|
const addr = document.getElementById('modbus_addr_input').value; |
|
|
|
|
|
const idt = document.getElementById('modbus_idt_input').value; |
|
|
if (modbusAddBtn && modbusFieldList && modbusRowTemplate) { |
|
|
const odt = document.getElementById('modbus_odt_input').value; |
|
|
modbusAddBtn.addEventListener('click', () => { |
|
|
const defaultValue = document.getElementById('modbus_dv_input').value; |
|
|
const field = document.getElementById('modbus_field_input').value; |
|
|
const shift = document.getElementById('modbus_shift_input').value; |
|
|
const addr = document.getElementById('modbus_addr_input').value; |
|
|
const expr = document.getElementById('modbus_expr_input').value; |
|
|
const idt = document.getElementById('modbus_idt_input').value; |
|
|
const mask = document.getElementById('modbus_mask_input').value; |
|
|
const odt = document.getElementById('modbus_odt_input').value; |
|
|
|
|
|
const defaultValue = document.getElementById('modbus_dv_input').value; |
|
|
const newRow = modbusRowTemplate.content.cloneNode(true); |
|
|
const shift = document.getElementById('modbus_shift_input').value; |
|
|
newRow.querySelector('.field-display').textContent = field; |
|
|
const expr = document.getElementById('modbus_expr_input').value; |
|
|
newRow.querySelector('.addr-display').textContent = addr; |
|
|
const mask = document.getElementById('modbus_mask_input').value; |
|
|
newRow.querySelector('.idt-display').textContent = idt; |
|
|
|
|
|
newRow.querySelector('.odt-display').textContent = odt; |
|
|
const newRow = modbusRowTemplate.content.cloneNode(true); |
|
|
newRow.querySelector('.dv-display').textContent = defaultValue; |
|
|
newRow.querySelector('.field-display').textContent = field; |
|
|
newRow.querySelector('.shift-display').textContent = shift; |
|
|
newRow.querySelector('.addr-display').textContent = addr; |
|
|
newRow.querySelector('.expr-display').textContent = expr; |
|
|
newRow.querySelector('.idt-display').textContent = idt; |
|
|
newRow.querySelector('.mask-display').textContent = mask; |
|
|
newRow.querySelector('.odt-display').textContent = odt; |
|
|
|
|
|
newRow.querySelector('.dv-display').textContent = defaultValue; |
|
|
modbusFieldList.appendChild(newRow); |
|
|
newRow.querySelector('.shift-display').textContent = shift; |
|
|
|
|
|
newRow.querySelector('.expr-display').textContent = expr; |
|
|
document.getElementById('modbus_field_input').value = ''; |
|
|
newRow.querySelector('.mask-display').textContent = mask; |
|
|
document.getElementById('modbus_addr_input').value = ''; |
|
|
|
|
|
document.getElementById('modbus_idt_input').value = ''; |
|
|
modbusFieldList.appendChild(newRow); |
|
|
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 () { |
|
|
document.getElementById('modbus_modify_btn').onclick = function () { |
|
|
if (!selectedRow) return alert('Please select the row you want to modify'); |
|
|
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 = ''); |
|
|
document.querySelectorAll('.modbus-input-area input').forEach(i => i.value = ''); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const opcUaAddBtn = document.getElementById('opcua_add_btn'); |
|
|
opcUaAddBtn.addEventListener('click', () => { |
|
|
const opcUaFieldList = document.getElementById('opcua-field-list'); |
|
|
const field = document.getElementById('opcua_field_input').value; |
|
|
const opcUaRowTemplate = document.getElementById('opcua-row-template'); |
|
|
const addr = document.getElementById('opcua_addr_input').value; |
|
|
|
|
|
const ns = document.getElementById('opcua_ns_input').value; |
|
|
if (opcUaAddBtn && opcUaFieldList && opcUaRowTemplate) { |
|
|
const odt = document.getElementById('opcua_odt_input').value; |
|
|
opcUaAddBtn.addEventListener('click', () => { |
|
|
const defaultValue = document.getElementById('opcua_dv_input').value; |
|
|
const field = document.getElementById('opcua_field_input').value; |
|
|
const shift = document.getElementById('opcua_shift_input').value; |
|
|
const addr = document.getElementById('opcua_addr_input').value; |
|
|
const expr = document.getElementById('opcua_expr_input').value; |
|
|
const ns = document.getElementById('opcua_ns_input').value; |
|
|
const mask = document.getElementById('opcua_mask_input').value; |
|
|
const odt = document.getElementById('opcua_odt_input').value; |
|
|
|
|
|
const defaultValue = document.getElementById('opcua_dv_input').value; |
|
|
const newRow = opcUaRowTemplate.content.cloneNode(true); |
|
|
const shift = document.getElementById('opcua_shift_input').value; |
|
|
newRow.querySelector('.opcua-field-display').textContent = field; |
|
|
const expr = document.getElementById('opcua_expr_input').value; |
|
|
newRow.querySelector('.opcua-addr-display').textContent = addr; |
|
|
const mask = document.getElementById('opcua_mask_input').value; |
|
|
newRow.querySelector('.opcua-ns-display').textContent = ns; |
|
|
|
|
|
newRow.querySelector('.opcua-odt-display').textContent = odt; |
|
|
const newRow = opcUaRowTemplate.content.cloneNode(true); |
|
|
newRow.querySelector('.opcua-dv-display').textContent = defaultValue; |
|
|
newRow.querySelector('.opcua-field-display').textContent = field; |
|
|
newRow.querySelector('.opcua-shift-display').textContent = shift; |
|
|
newRow.querySelector('.opcua-addr-display').textContent = addr; |
|
|
newRow.querySelector('.opcua-expr-display').textContent = expr; |
|
|
newRow.querySelector('.opcua-ns-display').textContent = ns; |
|
|
newRow.querySelector('.opcua-mask-display').textContent = mask; |
|
|
newRow.querySelector('.opcua-odt-display').textContent = odt; |
|
|
|
|
|
newRow.querySelector('.opcua-dv-display').textContent = defaultValue; |
|
|
opcUaFieldList.appendChild(newRow); |
|
|
newRow.querySelector('.opcua-shift-display').textContent = shift; |
|
|
|
|
|
newRow.querySelector('.opcua-expr-display').textContent = expr; |
|
|
document.getElementById('opcua_field_input').value = ''; |
|
|
newRow.querySelector('.opcua-mask-display').textContent = mask; |
|
|
document.getElementById('opcua_addr_input').value = ''; |
|
|
|
|
|
document.getElementById('opcua_ns_input').value = ''; |
|
|
opcUaFieldList.appendChild(newRow); |
|
|
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 () { |
|
|
document.getElementById('opcua_modify_btn').onclick = function () { |
|
|
if (!selectedRow) return alert('Please select the row you want to modify'); |
|
|
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 = ''); |
|
|
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', () => { |
|
|
document.getElementById('can_add_btn').addEventListener('click', () => { |
|
|
const field = document.getElementById('can_field_input').value; |
|
|
const field = document.getElementById('can_field_input').value; |
|
|
const id = document.getElementById('can_id_input').value; |
|
|
const id = document.getElementById('can_id_input').value; |
|
|
@ -1325,6 +1175,8 @@ |
|
|
document.getElementById('can_expr_input').value = ''; |
|
|
document.getElementById('can_expr_input').value = ''; |
|
|
document.getElementById('can_mask_input').value = ''; |
|
|
document.getElementById('can_mask_input').value = ''; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// can 테이블 수정 로직 |
|
|
document.getElementById('can_modify_btn').onclick = function () { |
|
|
document.getElementById('can_modify_btn').onclick = function () { |
|
|
if (!selectedRow) return alert('Please select the row you want to modify'); |
|
|
if (!selectedRow) return alert('Please select the row you want to modify'); |
|
|
selectedRow.querySelector('.can-field-display').textContent = document.getElementById('can_field_input').value; |
|
|
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-expr-display').textContent = document.getElementById('can_expr_input').value; |
|
|
selectedRow.querySelector('.can-mask-display').textContent = document.getElementById('can_mask_input').value; |
|
|
selectedRow.querySelector('.can-mask-display').textContent = document.getElementById('can_mask_input').value; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
//can 테이블 삭제 로직 |
|
|
document.getElementById('can_delete_btn').onclick = function () { |
|
|
document.getElementById('can_delete_btn').onclick = function () { |
|
|
if (!selectedRow) return alert('Select the row you want to delete'); |
|
|
if (!selectedRow) return alert('Select the row you want to delete'); |
|
|
const list = document.getElementById('can-field-list'); |
|
|
const list = document.getElementById('can-field-list'); |
|
|
@ -1343,7 +1197,7 @@ |
|
|
document.querySelectorAll('.can-input-area input').forEach(i => i.value = ''); |
|
|
document.querySelectorAll('.can-input-area input').forEach(i => i.value = ''); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 프로토콜에 따른 사이드바 변경 로직 |
|
|
regProtocolSelect = document.getElementById('reg_protocol'); |
|
|
regProtocolSelect = document.getElementById('reg_protocol'); |
|
|
if (regProtocolSelect) { |
|
|
if (regProtocolSelect) { |
|
|
regProtocolSelect.addEventListener('change', () => { |
|
|
regProtocolSelect.addEventListener('change', () => { |
|
|
@ -1353,6 +1207,7 @@ |
|
|
toggleProtocolMenus(); |
|
|
toggleProtocolMenus(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/*----서버로 데이터 전송하는 함수----*/ |
|
|
function saveToConfig() { |
|
|
function saveToConfig() { |
|
|
const deviceData = collectDeviceData(); |
|
|
const deviceData = collectDeviceData(); |
|
|
const protocolData = collectProtocolData(); |
|
|
const protocolData = collectProtocolData(); |
|
|
@ -1361,14 +1216,14 @@ |
|
|
alert("Register settings are currently invalid. Cannot be saved."); |
|
|
alert("Register settings are currently invalid. Cannot be saved."); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
//device |
|
|
// 두 개의 fetch를 병렬 실행 |
|
|
|
|
|
const deviceReq = fetch('http://localhost:8080/setting/device', { |
|
|
const deviceReq = fetch('http://localhost:8080/setting/device', { |
|
|
method: 'POST', |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify(deviceData) |
|
|
body: JSON.stringify(deviceData) |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
//protocol |
|
|
const protocolReq = fetch('http://localhost:8080/setting/protocol', { |
|
|
const protocolReq = fetch('http://localhost:8080/setting/protocol', { |
|
|
method: 'POST', |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
@ -1390,56 +1245,19 @@ |
|
|
alert('save failure'); |
|
|
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 => { |
|
|
document.querySelectorAll('.nav-items button').forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
btn.addEventListener('click', () => { |
|
|
showPage(btn.dataset.page); |
|
|
showPage(btn.dataset.page); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
//ssid 행 추가 버튼 |
|
|
document.getElementById('add-ssid').addEventListener('click', addSsidRow); |
|
|
document.getElementById('add-ssid').addEventListener('click', addSsidRow); |
|
|
|
|
|
|
|
|
|
|
|
//ssid 행 삭제 버튼 |
|
|
document.getElementById('delete-ssid').addEventListener('click', () => { |
|
|
document.getElementById('delete-ssid').addEventListener('click', () => { |
|
|
if (ssidList.children.length > 1) { |
|
|
if (ssidList.children.length > 1) { |
|
|
ssidList.removeChild(ssidList.lastElementChild); |
|
|
ssidList.removeChild(ssidList.lastElementChild); |
|
|
@ -1451,7 +1269,6 @@ |
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*-----device input데이터 받는 함수------*/ |
|
|
/*-----device input데이터 받는 함수------*/ |
|
|
function collectDeviceData() { |
|
|
function collectDeviceData() { |
|
|
const res = { ...data }; |
|
|
const res = { ...data }; |
|
|
@ -1536,6 +1353,7 @@ |
|
|
collectedRegister.MODBUS.push(obj); |
|
|
collectedRegister.MODBUS.push(obj); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// OPC_UA 테이블 수집 (빈 값은 필드에서 제외) |
|
|
// OPC_UA 테이블 수집 (빈 값은 필드에서 제외) |
|
|
const opcuaRows = document.querySelectorAll('#opcua-field-list tr'); |
|
|
const opcuaRows = document.querySelectorAll('#opcua-field-list tr'); |
|
|
collectedRegister.OPC_UA = []; |
|
|
collectedRegister.OPC_UA = []; |
|
|
@ -1561,6 +1379,7 @@ |
|
|
collectedRegister.OPC_UA.push(obj); |
|
|
collectedRegister.OPC_UA.push(obj); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// CAN 테이블 수집 (빈 값은 필드에서 제외) |
|
|
// CAN 테이블 수집 (빈 값은 필드에서 제외) |
|
|
const canRows = document.querySelectorAll('#can-field-list tr'); |
|
|
const canRows = document.querySelectorAll('#can-field-list tr'); |
|
|
collectedRegister.CAN = []; |
|
|
collectedRegister.CAN = []; |
|
|
@ -1670,6 +1489,7 @@ |
|
|
} |
|
|
} |
|
|
/*-----------------------------------------------------*/ |
|
|
/*-----------------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
/*------데이터상 ssid 추가 함수--------*/ |
|
|
function createSsidRow(obj = {wifi_ssid: '', wifi_password: '', wifi_security: 'none'}) { |
|
|
function createSsidRow(obj = {wifi_ssid: '', wifi_password: '', wifi_security: 'none'}) { |
|
|
const node = ssidTmpl.content.firstElementChild.cloneNode(true); |
|
|
const node = ssidTmpl.content.firstElementChild.cloneNode(true); |
|
|
node.querySelector('.ssid-name').value = obj.wifi_ssid ?? ''; |
|
|
node.querySelector('.ssid-name').value = obj.wifi_ssid ?? ''; |
|
|
@ -1677,7 +1497,9 @@ |
|
|
node.querySelector('select').value = (obj.wifi_security ?? 'none').toLowerCase(); |
|
|
node.querySelector('select').value = (obj.wifi_security ?? 'none').toLowerCase(); |
|
|
return node; |
|
|
return node; |
|
|
} |
|
|
} |
|
|
|
|
|
/*-----------------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
/*-------ssid 데이터 리스트 추가 함수-----------*/ |
|
|
function addSsidRow() { |
|
|
function addSsidRow() { |
|
|
ssidList.appendChild(createSsidRow()); |
|
|
ssidList.appendChild(createSsidRow()); |
|
|
} |
|
|
} |
|
|
@ -1694,7 +1516,9 @@ |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
/*-----------------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
/*-------ssid 리스트 병합 함수-------------*/ |
|
|
function gatherSsidRows() { |
|
|
function gatherSsidRows() { |
|
|
const arr = []; |
|
|
const arr = []; |
|
|
ssidList.querySelectorAll('.ssid-row').forEach(row => { |
|
|
ssidList.querySelectorAll('.ssid-row').forEach(row => { |
|
|
@ -1705,8 +1529,180 @@ |
|
|
}); |
|
|
}); |
|
|
return arr.length ? arr : [{wifi_ssid: '', wifi_password: '', wifi_security: 'none'}]; |
|
|
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'] |
|
|
const netFields = ['wifi_ip', 'wifi_netmask', 'wifi_gateway', 'wifi_dns1', 'wifi_dns2'] |
|
|
.map(id => document.getElementById(id)); |
|
|
.map(id => document.getElementById(id)); |
|
|
|
|
|
|
|
|
@ -1717,10 +1713,9 @@ |
|
|
el.classList.toggle('disabled', !isStatic); |
|
|
el.classList.toggle('disabled', !isStatic); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
/*-----------------------------------------------------*/ |
|
|
|
|
|
|
|
|
document.querySelectorAll('input[name="ipMode"]').forEach(r => r.addEventListener('change', toggleNetInputs)); |
|
|
/*-------imu-remap 정수와 부호 분리 함수-------------*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function axisValueToPair(val) { |
|
|
function axisValueToPair(val) { |
|
|
return val.startsWith('-') |
|
|
return val.startsWith('-') |
|
|
? {axis: val.slice(1), sign: 'minus'} |
|
|
? {axis: val.slice(1), sign: 'minus'} |
|
|
@ -1730,8 +1725,9 @@ |
|
|
function pairToAxisValue(axis, sign) { |
|
|
function pairToAxisValue(axis, sign) { |
|
|
return sign === 'minus' ? `-${axis}` : axis; |
|
|
return sign === 'minus' ? `-${axis}` : axis; |
|
|
} |
|
|
} |
|
|
|
|
|
/*-----------------------------------------------------*/ |
|
|
|
|
|
|
|
|
/* ───────────── 6. 데이터 → 화면 로딩 ───────────── */ |
|
|
/*--------초기 랜더링 화면 함수------------*/ |
|
|
function loadFromData(obj) { |
|
|
function loadFromData(obj) { |
|
|
/* Wi-Fi 모드 radio */ |
|
|
/* Wi-Fi 모드 radio */ |
|
|
document.querySelector(`input[name="ipMode"][value="${obj.wifi_static === 'on' ? 'static' : 'dhcp'}"]`).checked = true; |
|
|
document.querySelector(`input[name="ipMode"][value="${obj.wifi_static === 'on' ? 'static' : 'dhcp'}"]`).checked = true; |
|
|
@ -1756,9 +1752,11 @@ |
|
|
|
|
|
|
|
|
toggleNetInputs(); // 초기 잠금 상태 |
|
|
toggleNetInputs(); // 초기 잠금 상태 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
loadFromData(data); |
|
|
loadFromData(data); |
|
|
|
|
|
/*-----------------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*--------토굴 프로토콜 and can input 함수------------*/ |
|
|
let regProtocolSelect; |
|
|
let regProtocolSelect; |
|
|
|
|
|
|
|
|
function toggleProtocolMenus() { |
|
|
function toggleProtocolMenus() { |
|
|
@ -1784,7 +1782,9 @@ |
|
|
const protocolDetails = document.querySelectorAll('details.nav')[1]; |
|
|
const protocolDetails = document.querySelectorAll('details.nav')[1]; |
|
|
if (protocolDetails) protocolDetails.open = true; |
|
|
if (protocolDetails) protocolDetails.open = true; |
|
|
} |
|
|
} |
|
|
|
|
|
/*-----------------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
/*--------서버 DTO 기준 json 데이터 불러오는 함수------------------*/ |
|
|
function loadFromRegisterJson(register) { // register.json 데이터 로드 |
|
|
function loadFromRegisterJson(register) { // register.json 데이터 로드 |
|
|
if (!register) { |
|
|
if (!register) { |
|
|
console.error("Register data is null or undefined."); |
|
|
console.error("Register data is null or undefined."); |
|
|
@ -1903,7 +1903,6 @@ |
|
|
const newRow = canRowTemplate.content.cloneNode(true); |
|
|
const newRow = canRowTemplate.content.cloneNode(true); |
|
|
canFieldList.appendChild(newRow); |
|
|
canFieldList.appendChild(newRow); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
</script> |
|
|
</script> |
|
|
</body> |
|
|
</body> |
|
|
|