You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2433 lines
104 KiB

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Device Configuration UI</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box
}
body {
font-family: "Pretendard", sans-serif;
min-height: 100vh;
display: flex;
background: #fff;
color: #111
}
/* ───── Sidebar ───── */
.sidebar {
width: 260px;
border-right: 1px solid #ddd;
padding: 32px 24px;
display: flex;
flex-direction: column
}
.sidebar h2 {
font-size: 24px;
margin-bottom: 32px
}
details.nav {
margin-bottom: 24px
}
.nav > summary {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
font-size: 20px
}
.nav > summary::before {
content: "";
width: 8px;
height: 8px;
margin-right: 8px;
border-left: 2px solid #111;
border-bottom: 2px solid #111;
transform: rotate(-135deg);
transition: .2s
}
.nav[open] > summary::before {
transform: rotate(-45deg)
}
.nav-items {
display: flex;
flex-direction: column;
margin-top: 10px;
padding-left: 16px;
gap: 6px
}
.nav-items button {
background: none;
border: none;
cursor: pointer;
font-size: 16px;
text-align: left;
padding: 4px 0
}
/* ───── Top-bar ───── */
.topbar {
height: 68px;
background: #d9d9d9;
display: flex;
align-items: center;
padding: 0 32px;
font-size: 28px;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0, 0, 0, .15) inset
}
/* ───── Main Board ───── */
.content {
flex: 1;
display: flex;
flex-direction: column
}
.board {
flex: 1;
overflow: auto;
padding: 40px;
display: flex;
flex-direction: column;
gap: 32px
}
.card {
background: #fff;
border: 1px solid #e5e5e5;
box-shadow: 0 2px 4px rgba(0, 0, 0, .15) inset;
padding: 32px;
border-radius: 8px
}
.card h3 {
font-size: 24px;
margin-bottom: 24px
}
/* SSID rows */
.ssid-row {
display: grid;
grid-template-columns:220px 220px 60px 120px;
gap: 16px;
margin-bottom: 12px
}
.ssid-row input, .ssid-row select {
padding: 6px 8px;
font-size: 16px;
border: 1px solid #aaa;
border-radius: 4px;
width: 100%
}
.ssid-row button {
padding: 6px 10px;
font-size: 14px;
background: #f1f1f1;
border: 1px solid #777;
border-radius: 4px;
cursor: pointer
}
/* 3-column grids (Network / Server) */
.grid-3-col {
display: grid;
grid-template-columns:180px 240px 120px;
align-items: center;
gap: 16px
}
.grid-3-col input {
padding: 6px 8px;
font-size: 16px;
border: 1px solid #aaa;
border-radius: 1px;
width: 100%
}
/* radio */
.radio-group {
display: flex;
align-items: center;
gap: 24px
}
.radio-label {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer
}
.radio-label input {
margin: 0
}
/* I/O */
.io-top {
display: grid;
grid-template-columns:repeat(auto-fit, minmax(360px, 1fr));
gap: 40px;
margin-bottom: 32px
}
.io-bot {
display: grid; /* Ethernet·LTE와 같은 2열(가변) 그리드 */
grid-template-columns:repeat(auto-fit, minmax(360px, 1fr));
gap: 40px;
}
.io-group {
display: flex;
flex-direction: column;
gap: 8px
}
.io-group h4 {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px
}
.io-group h4::before {
content: "";
width: 8px;
height: 8px;
background: #aaa;
display: inline-block
}
.io-fieldset {
border: 1px solid #bbb;
padding: 24px 32px;
border-radius: 4px;
display: flex;
flex-direction: column;
gap: 16px;
width: 100%
}
.io-row {
display: grid;
grid-template-columns:120px 12px 1fr;
align-items: center;
gap: 8px
}
.io-row input, .io-row select {
padding: 4px 6px;
border: 1px solid #aaa;
border-radius: 4px;
font-size: 14px;
width: 50%
}
.io-row span.label {
font-size: 14px;
color: #333;
display: block;
width: 100%;
}
.io-row span.label-inline {
font-size: 14px;
color: #333;
display: inline;
width: auto
}
.io-row span.colon {
font-size: 14px;
color: #333;
text-align: center;
}
.reg-io-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px
}
.reg-io-grid .io-fieldset .io-row {
display: grid;
grid-template-columns: 140px 220px;
align-items: center;
gap: 4px;
margin-bottom: 8px;
}
/* config 내부 라벨 */
.reg-io-grid .io-fieldset .io-row span.label {
font-size: 14px;
color: #333;
}
.reg-io-grid .io-fieldset .io-row input,
.reg-io-grid .io-fieldset .io-row select {
padding: 4px 6px;
border: 1px solid #aaa;
border-radius: 4px;
font-size: 14px;
width: 100%;
box-sizing: border-box;
}
.reg-io-grid .io-fieldset .io-row.checkbox-group {
display: flex;
align-items: center;
gap: 16px;
grid-column: 2 / 3;
}
.reg-io-grid .io-fieldset .io-row .checkbox-with-text {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
.reg-io-grid .io-fieldset .io-row .checkbox-with-text input[type="checkbox"] {
width: auto;
margin: 0;
}
/* 비활성 input 시각화 */
.disabled {
background: #f5f5f5;
cursor: not-allowed
}
/* Footer Buttons */
.actions {
display: flex;
gap: 24px;
justify-content: center;
padding: 20px
}
.actions button {
min-width: 140px;
padding: 10px 18px;
font-size: 16px;
border: 1px solid #bbb;
background: #d9d9d9;
border-radius: 4px;
cursor: pointer
}
.config-actions {
display: flex;
gap: 24px;
justify-content: center;
padding: 20px
}
.config-actions button {
min-width: 140px;
padding: 10px 18px;
font-size: 16px;
border: 1px solid #bbb;
background: #d9d9d9;
border-radius: 4px;
cursor: pointer
}
/* MODBUS 화면 심플 스타일 */
#page-modbus .card {
background: #f7f7f7;
border: 1px solid #bbb;
box-shadow: none;
padding: 24px;
border-radius: 4px;
display: flex;
flex-direction: column;
}
#page-modbus table {
width: 100%;
border-collapse: collapse;
background: #fff;
border: 1px solid #bbb;
}
#page-modbus th, #page-modbus td {
border: 1px solid #bbb;
padding: 6px 8px;
text-align: left;
background: #fff;
}
#page-modbus th {
background: #e5e5e5;
font-weight: 500;
}
#page-modbus .modbus-input-area {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
gap: 20px 20px;
margin: 24px auto 16px auto;
background: #eaeaea;
padding: 16px;
border-radius: 4px;
margin-left: auto;
margin-right: auto;
}
#page-modbus .input-row {
display: contents;
}
#page-modbus .modbus-input-area label.nowrap {
white-space: nowrap;
}
#page-modbus .modbus-input-area label {
min-width: auto;
color: #444;
white-space: normal;
}
#page-modbus .modbus-input-area input {
background: #e0e0e0;
border: none;
border-radius: 2px;
padding: 4px 8px;
min-width: auto;
}
#page-modbus .modbus-actions {
display: flex;
gap: 24px;
justify-content: center;
margin: 16px 0 0 0;
}
#page-modbus .modbus-actions button {
background: #d3d3d3;
border: 1px solid #aaa;
border-radius: 2px;
padding: 8px 24px;
font-size: 16px;
cursor: pointer;
}
/* 하단 버튼 영역 */
#modbus-bottom-actions {
display: flex;
gap: 24px;
justify-content: center;
background: #f7f7f7;
border: 1px solid #bbb;
border-radius: 4px;
margin: 24px 0 0 0;
padding: 20px 0;
}
#modbus-bottom-actions button {
background: #e0e0e0;
border: 1px solid #aaa;
border-radius: 2px;
padding: 10px 18px;
font-size: 16px;
min-width: 140px;
cursor: pointer;
}
/* OPC_UA 화면 */
#page-opcua .card {
background: #f7f7f7;
border: 1px solid #bbb;
box-shadow: none;
padding: 24px;
border-radius: 4px;
display: flex;
flex-direction: column;
}
#page-opcua table {
width: 100%;
border-collapse: collapse;
background: #fff;
border: 1px solid #bbb;
}
#page-opcua th, #page-opcua td {
border: 1px solid #bbb;
padding: 6px 8px;
text-align: left;
background: #fff;
}
#page-opcua th {
background: #e5e5e5;
font-weight: 500;
}
#page-opcua .opcua-input-area {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
gap: 20px 20px;
margin: 24px auto 16px auto;
background: #eaeaea;
padding: 16px;
border-radius: 4px;
margin-left: auto;
margin-right: auto;
}
#page-opcua .input-row {
display: contents;
}
#page-opcua .opcua-input-area label.nowrap {
white-space: nowrap;
}
#page-opcua .opcua-input-area label {
min-width: auto;
color: #444;
white-space: normal;
}
#page-opcua .opcua-input-area input {
background: #e0e0e0;
border: none;
border-radius: 2px;
padding: 4px 8px;
min-width: auto;
}
#page-opcua .opcua-actions {
display: flex;
gap: 24px;
justify-content: center;
margin: 16px 0 0 0;
}
#page-opcua .opcua-actions button {
background: #d3d3d3;
border: 1px solid #aaa;
border-radius: 2px;
padding: 8px 24px;
font-size: 16px;
cursor: pointer;
}
#opcUa-bottom-actions {
display: flex;
gap: 24px;
justify-content: center;
background: #f7f7f7;
border: 1px solid #bbb;
border-radius: 4px;
margin: 24px 0 0 0;
padding: 20px 0;
}
#opcUa-bottom-actions button {
background: #e0e0e0;
border: 1px solid #aaa;
border-radius: 2px;
padding: 10px 18px;
font-size: 16px;
min-width: 140px;
cursor: pointer;
}
/* CAN 화면 스타일 */
#page-can .card {
background: #f7f7f7;
border: 1px solid #bbb;
box-shadow: none;
padding: 24px;
border-radius: 4px;
display: flex;
flex-direction: column;
}
#page-can table {
width: 100%;
border-collapse: collapse;
background: #fff;
border: 1px solid #bbb;
}
#page-can th, #page-can td {
border: 1px solid #bbb;
padding: 6px 8px;
text-align: left;
background: #fff;
}
#page-can th {
background: #e5e5e5;
font-weight: 500;
}
#page-can .can-input-area {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
gap: 20px 20px;
margin: 24px auto 16px auto;
background: #eaeaea;
padding: 16px;
border-radius: 4px;
margin-left: auto;
margin-right: auto;
}
#page-can .input-row {
display: contents;
}
#page-can .can-input-area label.nowrap {
white-space: nowrap;
}
#page-can .can-input-area label {
min-width: auto;
color: #444;
white-space: normal;
}
#page-can .can-input-area input {
background: #e0e0e0;
border: none;
border-radius: 2px;
padding: 4px 8px;
min-width: auto;
}
#page-can .can-actions {
display: flex;
gap: 24px;
justify-content: center;
margin: 16px 0 0 0;
}
#page-can .can-actions button {
background: #d3d3d3;
border: 1px solid #aaa;
border-radius: 2px;
padding: 8px 24px;
font-size: 16px;
cursor: pointer;
}
#can-bottom-actions {
display: flex;
gap: 24px;
justify-content: center;
background: #f7f7f7;
border: 1px solid #bbb;
border-radius: 4px;
margin: 24px 0 0 0;
padding: 20px 0;
}
#can-bottom-actions button {
background: #e0e0e0;
border: 1px solid #aaa;
border-radius: 2px;
padding: 10px 18px;
font-size: 16px;
min-width: 140px;
cursor: pointer;
}
.selected-row {
background: #d0e6ff !important;
}
.selected-row > td {
background: #d0e6ff !important;
}
1 year ago
</style>
</head>
<body>
<!-- ───── Sidebar ───── -->
<aside class="sidebar">
<h2>Configuration</h2>
<details class="nav" open>
<summary>Device configuration</summary>
<div class="nav-items">
<button data-page="ssid">SSID</button>
<button data-page="network">Network &amp; Server</button>
<button data-page="io">I/O</button>
</div>
</details>
<details class="nav">
<summary>Protocol configuration</summary>
<div class="nav-items">
<button data-page="reg">Config</button>
<button data-page="modbus" id="modbusSidebarBtn" style="display: none">MODBUS</button>
<button data-page="opcua" id="opcUaSidebarBtn" style="display: none">OPC_UA</button>
<button data-page="can" id="canSidebarBtn" style="display: none">CAN</button>
</div>
</details>
</aside>
<!-- ───── Content ───── -->
<div class="content">
<header class="topbar" id="page-title">SSID</header>
<main class="board">
<!-- ▽ SSID PAGE ▽ -->
<section id="page-ssid" class="page">
<div class="card">
<h3>SSID List</h3>
<template id="ssid-template">
<div class="ssid-row">
<input class="ssid-name" placeholder="Name"/>
<input class="ssid-pw" type="password" placeholder="Password"/>
<button type="button" class="toggle-pw">show</button>
<select>
<option value="none">NONE</option>
<option value="wpa/wpa2">WPA/WPA2</option>
</select>
</div>
</template>
<div id="ssid-list"></div>
<button id="add-ssid" style="margin-top:12px">Add SSID</button>
1 year ago
<button id="delete-ssid" style="margin-top:12px">Delete SSID</button>
</div>
<div class="actions">
<button id="loadDeviceBtn_ssid">Load Device File</button>
<button id="saveDeviceBtn_ssid">Save to Device</button>
<button id="downloadDeviceBtn">Download Device File</button>
</div>
</section>
<!-- ▽ NETWORK PAGE ▽ -->
<section id="page-network" class="page" style="display:none">
<div class="card" style="margin-bottom:32px">
<h3>Network</h3>
<div class="grid-3-col" style="margin-bottom:16px">
<div class="radio-group">
<label class="radio-label"><input type="radio" name="ipMode" value="static"> Static</label>
<label class="radio-label"><input type="radio" name="ipMode" value="dhcp"> DHCP</label>
</div>
</div>
<div class="grid-3-col"><span>IP</span><input id="wifi_ip" placeholder="000.000.000.000"/></div>
<div class="grid-3-col"><span>SUBNET</span><input id="wifi_netmask" placeholder="255.255.255.0"/></div>
<div class="grid-3-col"><span>GATEWAY</span><input id="wifi_gateway" placeholder="000.000.000.001"/>
</div>
<div class="grid-3-col"><span>DNS #1</span><input id="wifi_dns1" placeholder="008.008.008.008"/></div>
<div class="grid-3-col"><span>DNS #2</span><input id="wifi_dns2" placeholder="008.008.004.004"/></div>
</div>
<div class="card">
<h3>Server</h3>
<div class="grid-3-col"><strong></strong><span style="text-align:center">IP</span><span
style="text-align:center">Port</span></div>
<div class="grid-3-col"><span>TIoT Server</span> <input id="rtcm_server_ip"/><input
id="rtcm_server_port"/></div>
<div class="grid-3-col"><span>Update Server</span><input id="update_server_ip"/><input
id="update_server_port"/></div>
<div class="grid-3-col"><span>DGPS Server</span> <input id="modbus_server_ip"/><input
id="modbus_server_port"/></div>
<div class="grid-3-col"><span>OPC-UA</span> <input id="opc_ua_server_ip"/><input
id="opc_ua_server_port"/></div>
</div>
<div class="actions">
<button id="loadDeviceBtn_network">Load Device File</button>
<button id="saveDeviceBtn_network">Save to Device</button>
<button id="downloadDeviceBtn">Download Device File</button>
</div>
</section>
<!-- ▽ I/O PAGE ▽ -->
<section id="page-io" class="page" style="display:none">
<div class="card">
<div class="io-top">
<!-- Ethernet -->
<div class="io-group">
<h4>Ethernet</h4>
<div class="io-fieldset">
<div class="io-row"><span class="label-inline">IP</span><span class="colon">:</span><input
id="eth_ip"></div>
<div class="io-row"><span class="label-inline">SUBNET</span><span
class="colon">:</span><input id="eth_netmask"></div>
<div class="io-row"><span class="label-inline">GATEWAY</span><span
class="colon">:</span><input id="eth_gateway"></div>
<div class="io-row"><span class="label-inline">SERVER IP</span><span
class="colon">:</span><input id="protocol_server_ip"></div>
<div class="io-row"><span class="label-inline">SERVER PORT</span><span
class="colon">:</span><input id="protocol_server_port"></div>
</div>
</div>
<!-- LTE -->
<div class="io-group">
<h4>LTE</h4>
<div class="io-fieldset">
<div class="io-row"><span class="label-inline">IP</span><span class="colon">:</span><input
id="lte_ip"></div>
<div class="io-row"><span class="label-inline">SUBNET</span><span
class="colon">:</span><input id="lte_subnet"></div>
<div class="io-row"><span class="label-inline">GATEWAY</span><span
class="colon">:</span><input id="lte_gateway"></div>
<div class="io-row"><span class="label-inline">Port</span><span class="colon">:</span><input
id="lte_port"></div>
</div>
</div>
</div>
<div class="io-bot">
<!-- RS-485 -->
<div class="io-group">
<h4>MODBUS 422 / 485</h4>
<div class="io-fieldset">
<div class="io-row"><span class="label-inline">Mode</span><span class="colon">:</span>
<select id="rs485_mode">
<option value="half">half</option>
<option value="full">full</option>
</select>
</div>
<div class="io-row"><span class="label-inline">Speed</span><span
class="colon">:</span><select id="rs485_baudrate">
<option value="9600">9600</option>
<option value="115200">115200</option>
</select></div>
<div class="io-row"><span class="label-inline">Parity</span><span
class="colon">:</span><select id="rs485_parity">
<option value="no">NONE</option>
<option value="odd">ODD</option>
<option value="even">EVEN</option>
</select></div>
<div class="io-row"><span class="label-inline">Data bits</span><span
class="colon">:</span><input id="rs485_data_bits"></div>
<div class="io-row"><span class="label-inline">Stop bits</span><span
class="colon">:</span><input id="rs485_stop_bits"></div>
</div>
</div>
<!-- CAN -->
<div class="io-group">
<h4>CAN</h4>
<div class="io-fieldset">
<div class="io-row"><span class="label-inline">Type</span><span class="colon">:</span>
<select id="can_bus_type">
<option value="extended">extended</option>
<option value="standard">standard</option>
</select>
</div>
<div class="io-row"><span class="label-inline">Speed</span><span class="colon">:</span>
<select id="can_baudrate">
<option value="1000">100kb/s</option>
<option value="2500">250kb/s</option>
<option value="5000">500kb/s</option>
<option value="10000">1M/s</option>
</select>
</div>
1 year ago
</div>
<!-- IMU -->
<div class="io-group">
<h4>IMU Re-map</h4>
<div class="io-fieldset">
<div class="io-row"><span class="label-inline">X</span><span class="colon">:</span>
<select id="imu_remap_x">
<option value="x">x</option>
<option value="y">y</option>
<option value="z">z</option>
<option value="-x">-x</option>
<option value="-y">-y</option>
<option value="-z">-z</option>
</select>
</div>
<div class="io-row"><span class="label-inline">Y</span><span
class="colon">:</span><select id="imu_remap_y">
1 year ago
<option value="x">x</option>
<option value="y">y</option>
<option value="z">z</option>
<option value="-x">-x</option>
<option value="-y">-y</option>
<option value="-z">-z</option>
</select></div>
<div class="io-row"><span class="label-inline">Z</span><span
class="colon">:</span><select id="imu_remap_z">
<option value="x">x</option>
<option value="y">y</option>
<option value="z">z</option>
<option value="-x">-x</option>
<option value="-y">-y</option>
<option value="-z">-z</option>
</select></div>
1 year ago
</div>
</div>
</div>
</div>
</div>
<div class="actions">
<button id="loadDeviceBtn_io">Load Device File</button>
<button id="saveDeviceBtn_io">Save to Device</button>
<button id="downloadDeviceBtn">Download Device File</button>
</div>
</section>
<!-- ▽ Register PAGE (placeholder) ▽ -->
<section id="page-reg" class="page" style="display:none">
<div class="card"><h3>Config</h3>
<div class="io-group">
<div class="reg-io-grid">
<div class="io-fieldset">
<div class="io-row"><span class="label">Device type</span>
<select id="reg_device_type">
<option selected disabled hidden="hidden"></option>
<option value="TIOT">TIOT</option>
<option value="RTLS">RTLS</option>
</select>
</div>
<div class="io-row"><span class="label">Version</span><input id="reg_version"></div>
<div class="io-row"><span class="label">Equipment</span>
<select id="reg_equipment">
<option selected disabled hidden="hidden"></option>
<option value="ITV">ITV</option>
<option value="RS">RS</option>
<option value="ECH">ECH</option>
<option value="RTG">RTG</option>
<option value="RMG">RMG</option>
<option value="MHC">MHC</option>
<option value="STS">STS</option>
</select>
</div>
<div class="io-row"><span class="label">Equipment ID</span><input id="reg_equipment_id">
</div>
<div class="io-row"><span class="label">MEID</span><input id="reg_meid"></div>
<div class="io-row"><span class="label">Protocol</span>
<select id="reg_protocol">
<option selected disabled hidden="hidden"></option>
<option value="MODBUS">MODBUS</option>
<option value="OPC_UA">OPC_UA</option>
</select>
</div>
<div class="io-row"><span class="label">heading_on</span>
<select id="reg_heading_on">
<option selected disabled hidden="hidden"></option>
<option value="on">on</option>
<option value="off">off</option>
</select>
</div>
<div class="io-row"><span class="label">heading_imu_on</span>
<select id="reg_heading_imu_on">
<option selected disabled hidden="hidden"></option>
<option value="on">on</option>
<option value="off">off</option>
</select>
</div>
</div>
<div class="io-fieldset">
<div class="io-row"><span class="label">DR On/Off</span>
<select id="reg_dr_on_off">
<option selected disabled hidden="hidden"></option>
<option value="on">on</option>
<option value="off">off</option>
</select>
</div>
<div class="io-row"><span class="label">Speed Data Source</span>
<select id="reg_speed_data_source">
<option selected disabled hidden="hidden"></option>
<option value="CAN">CAN</option>
<option value="GPS">GPS</option>
</select>
</div>
<div class="io-row"><span class="label">2Byte Order</span>
<select id="reg_two_byte_order">
<option selected disabled hidden="hidden"></option>
<option value="little">little</option>
<option value="big">big</option>
</select>
</div>
<div class="io-row"><span class="label">4Byte Order</span>
<select id="reg_four_byte_order">
<option selected disabled hidden="hidden"></option>
<option value="little">little</option>
<option value="littleSwap">little swap</option>
<option value="bigSwap">big swap</option>
<option value="big">big</option>
</select>
</div>
<div class="io-row"><span class="label">CAN Input</span>
<select id="reg_can_input">
<option selected disabled hidden="hidden"></option>
<option value="on">on</option>
<option value="off">off</option>
</select>
</div>
<div class="io-row"><span class="label">fixmode_on</span>
<select id="reg_fixmode_on">
<option selected disabled hidden="hidden"></option>
<option value="on">on</option>
<option value="off">off</option>
</select>
</div>
<div class="io-row"><span class="label">Analog Input Level</span>
<select id="reg_analog_input_level">
<option selected disabled hidden="hidden"></option>
<option value="2">2</option>
<option value="4">4</option>
<option value="6">6</option>
</select>
</div>
<div class="io-row checkbox-group">
<label class="checkbox-with-text">
<input type="checkbox" id="ai0"/>AI0
<input type="checkbox" id="ai1"/>AI1
<input type="checkbox" id="ai2"/>AI2
<input type="checkbox" id="ai3"/>AI3
</label>
</div>
<div class="io-row checkbox-group">
<label class="checkbox-with-text">
<input type="checkbox" id="di0"/>DI0
<input type="checkbox" id="di1"/>DI1
<input type="checkbox" id="di2"/>DI2
<input type="checkbox" id="di3"/>DI3
</label>
</div>
</div>
</div>
</div>
</div>
<div class="config-actions">
<button id="loadProtocolBtn">Load Protocol File</button>
<button id="saveProtocolBtn">Save to Protocol</button>
<button id="uploadProtocolBtn">Upload to Device</button>
<button id="downloadProtocolBtn">Download Device File</button>
</div>
</section>
1 year ago
<template id="modbus-row-template">
<tr>
<td><span class="field-display"></span></td>
<td><span class="addr-display"></span></td>
<td><span class="idt-display"></span></td>
<td><span class="odt-display"></span></td>
<td><span class="dv-display"></span></td>
<td><span class="shift-display"></span></td>
<td><span class="expr-display"></span></td>
<td><span class="mask-display"></span></td>
</tr>
</template>
<section id="page-modbus" class="page" style="display:none;">
<div class="card">
1 year ago
<h3>Register_Configuration Modbus화면</h3>
<div class="modbus-table-container">
<table>
<thead>
<tr>
1 year ago
<th>Field</th>
<th>Address</th>
<th>IDT</th>
<th>ODT</th>
<th>Default Value</th>
<th>Shift</th>
<th>Expression</th>
<th>Mask</th>
</tr>
</thead>
<tbody id="modbus-field-list">
</tbody>
</table>
</div>
<div class="modbus-input-area">
1 year ago
<label>Field</label>
<input type="text" id="modbus_field_input" placeholder="Enter field name">
<label class="nowrap">idt (Input Data Type)</label>
<select id="modbus_idt_input">
<option selected disabled hidden="hidden"></option>
<option value="integer">integer</option>
<option value="float64">float64</option>
<option value="boolean">boolean</option>
<option value="unsignedInteger">unsignedInteger</option>
<option value="short">short</option>
<option value="unsignedShort">unsignedShort</option>
</select>
1 year ago
<label>addr</label>
<input type="text" id="modbus_addr_input" placeholder="Enter address">
<label class="nowrap">odt (Output Data Type)</label>
<select id="modbus_odt_input">
<option selected disabled hidden="hidden"></option>
<option value="integer">integer</option>
<option value="float64">float64</option>
<option value="boolean">boolean</option>
<option value="string">string</option>
</select>
1 year ago
<label>shift</label>
<input type="text" id="modbus_shift_input" placeholder="Enter shift value">
<label class="nowrap">dv</label>
<input type="text" id="modbus_dv_input" placeholder="Enter default value">
<label>expr</label>
<input type="text" id="modbus_expr_input" placeholder="Enter expression">
<label class="nowrap">mask</label>
<input type="text" id="modbus_mask_input" placeholder="Enter mask">
</div>
<div class="modbus-actions">
<button id="modbus_add_btn">Add</button>
<button id="modbus_modify_btn">Modify</button>
<button id="modbus_delete_btn">Delete</button>
</div>
</div>
<div id="modbus-bottom-actions">
<button id="loadModbusBtn">Load Protocol File</button>
<button id="saveModbusBtn">Save Protocol File</button>
<button id="uploadModbusBtn">Upload to Device</button>
<button id="downloadProtocolBtn">Download Device File</button>
</div>
</section>
<template id="opcua-row-template">
<tr class="opcua-row">
<td><span class="opcua-field-display"></span></td>
<td><span class="opcua-addr-display"></span></td>
<td><span class="opcua-ns-display"></span></td>
<td><span class="opcua-odt-display"></span></td>
<td><span class="opcua-dv-display"></span></td>
<td><span class="opcua-shift-display"></span></td>
<td><span class="opcua-mask-display"></span></td>
<td><span class="opcua-expr-display"></span></td>
</tr>
</template>
<section id="page-opcua" class="page" style="display:none;">
<div class="card">
<h3>OPC_UA Configuration</h3>
<div class="opcua-table-container">
<table>
<thead>
<tr>
<th>Field</th>
<th>Address</th>
<th>NS</th>
<th>ODT</th>
<th>Default Value</th>
<th>Shift</th>
<th>Expression</th>
<th>Mask</th>
</tr>
</thead>
<tbody id="opcua-field-list">
</tbody>
</table>
</div>
<div class="opcua-input-area">
<label>field</label>
<input type="text" id="opcua_field_input" placeholder="Enter field name">
<label class="nowrap">ns(NameSpace)</label>
<input type="text" id="opcua_ns_input" placeholder="Enter NS">
<label>addr(address)</label>
<input type="text" id="opcua_addr_input" placeholder="Enter address">
<label class="nowrap">odt(Output Data Type)</label>
<select id="opcua_odt_input">
<option selected disabled hidden="hidden"></option>
<option value="integer">integer</option>
<option value="float64">float64</option>
<option value="boolean">boolean</option>
<option value="string">string</option>
</select>
<label>shift</label>
<input type="text" id="opcua_shift_input" placeholder="Enter shift value">
<label class="nowrap">dv(Default Value)</label>
<input type="text" id="opcua_dv_input" placeholder="Enter default value">
<label>expr</label>
<input type="text" id="opcua_expr_input" placeholder="Enter expression">
<label class="nowrap">mask</label>
<input type="text" id="opcua_mask_input" placeholder="Enter mask">
</div>
<div class="opcua-actions">
<button id="opcua_add_btn">Add</button>
<button id="opcua_modify_btn">Modify</button>
<button id="opcua_delete_btn">Delete</button>
</div>
</div>
<div id="opcUa-bottom-actions">
<button id="loadOpcUaFileBtn">Load Protocol File</button>
<button id="saveOpcUaFileBtn">Save to Protocol</button>
<button id="uploadOpcuaFileBtn">Upload to Device</button>
<button id="downloadProtocolBtn">Download Device File</button>
</div>
</section>
<template id="can-row-template">
<tr class="can-row">
<td><span class="can-field-display"></span></td>
<td><span class="can-id-display"></span></td>
<td><span class="can-odt-display"></span></td>
<td><span class="can-dv-display"></span></td>
<td><span class="can-shift-display"></span></td>
<td><span class="can-mask-display"></span></td>
<td><span class="can-expr-display"></span></td>
</tr>
</template>
<section id="page-can" class="page" style="display:none;">
<div class="card">
<h3>CAN Configuration</h3>
<div class="can-table-container">
<table>
<thead>
<tr>
<th>Field</th>
<th>ID</th>
<th>ODT</th>
<th>Default Value</th>
<th>Shift</th>
<th>Expression</th>
<th>Mask</th>
</tr>
</thead>
<tbody id="can-field-list">
</tbody>
</table>
</div>
<div class="can-input-area">
<label>Field</label>
<input type="text" id="can_field_input" placeholder="Enter field name">
<label>ID</label>
<input type="text" id="can_id_input" placeholder="Enter ID">
<label>odt (Output Data Type)</label>
<select id="can_odt_input">
<option selected disabled hidden="hidden"></option>
<option value="integer">integer</option>
<option value="float64">float64</option>
<option value="boolean">boolean</option>
<option value="string">string</option>
</select>
<label>dv</label>
<input type="text" id="can_dv_input" placeholder="Enter default value">
<label>shift</label>
<input type="text" id="can_shift_input" placeholder="Enter shift value">
<label>expr</label>
<input type="text" id="can_expr_input" placeholder="Enter expression">
<label>mask</label>
<input type="text" id="can_mask_input" placeholder="Enter mask">
</div>
<div class="can-actions">
<button id="can_add_btn">Add</button>
<button id="can_modify_btn">Modify</button>
<button id="can_delete_btn">Delete</button>
</div>
</div>
<div id="can-bottom-actions">
<button id="loadCanBtn">Load Protocol File</button>
<button id="saveCanBtn">Save Protocol File</button>
<button id="uploadCanBtn">Upload to Device</button>
<button id="downloadProtocolBtn">Download Device File</button>
</div>
</section>
</main>
</div>
<!-- ───── Script ───── -->
<script>
const data = {
/* Wi-Fi */
wifi_static: 'off', wifi_ip: '', wifi_netmask: '', wifi_gateway: '', wifi_dns1: '', wifi_dns2: '',
/* Ethernet */
eth_ip: '', eth_netmask: '', eth_gateway: '',
/* 서버들 */
protocol_server_ip: '', protocol_server_port: null,
update_server_ip: '',
update_server_port: null,
rtcm_server_ip: '',
rtcm_server_port: null,
opc_ua_server_ip: '',
opc_ua_server_port: null,
modbus_server_ip: '',
modbus_server_port: null,
/* CAN / RS-485 */
can_bus_type: '', can_baudrate: null,
rs485_mode: '', rs485_baudrate: null, rs485_data_bits: null,
rs485_parity: '', rs485_stop_bits: null,
/* IMU (축 + 부호) */
imu_remap_x: '', imu_remap_y: '', imu_remap_z: '',
imu_remap_x_sign: '', imu_remap_y_sign: '', imu_remap_z_sign: '',
/* LTE 설정 */
lte_ip: '', lte_subnet: '', lte_gateway: '', lte_port: null,
/* SSID 목록 */
WIFI_SSID: [{wifi_ssid: '', wifi_password: '', wifi_security: 'none'}],
};
const configData = {
analog_input_level: '',
ai0: 0,
ai1: 0,
ai2: 0,
ai3: 0,
di0: 0,
di1: 0,
di2: 0,
di3: 0,
can_input: '',
device_type: '',
dr_on: '',
equipment: '',
two_byte: '',
four_byte: '',
input_data_type: '',
output_data_type: '',
protocol: '',
speed_data_source: '',
version: '',
equipment_id: '',
meid: '',
heading_on: '',
heading_imu_on: '',
fixmode_on: '',
MODBUS: [],
OPC_UA: [],
CAN: []
};
const configIdMap = {
'reg_analog_input_level': 'analog_input_level',
'ai0': 'ai0',
'ai1': 'ai1',
'ai2': 'ai2',
'ai3': 'ai3',
'di0': 'di0',
'di1': 'di1',
'di2': 'di2',
'di3': 'di3',
'reg_can_input': 'can_input',
'reg_device_type': 'device_type',
'reg_dr_on_off': 'dr_on',
'reg_equipment': 'equipment',
'reg_two_byte_order': 'two_byte',
'reg_four_byte_order': 'four_byte',
'reg_protocol': 'protocol',
'reg_speed_data_source': 'speed_data_source',
'reg_version': 'version',
'reg_equipment_id': 'equipment_id',
'reg_meid': 'meid',
'reg_heading_on': 'heading_on',
'reg_heading_imu_on': 'heading_imu_on',
'reg_fixmode_on': 'fixmode_on'
};
let lastLoadedRegisterData = {};
const pages = {
ssid: document.getElementById('page-ssid'),
network: document.getElementById('page-network'),
io: document.getElementById('page-io'),
reg: document.getElementById('page-reg'),
modbus: document.getElementById('page-modbus'),
opcua: document.getElementById('page-opcua'),
can: document.getElementById('page-can')
};
const titleEl = document.getElementById('page-title');
function downloadDeviceJson(obj, filename = 'deviceConfig.json') {
const json = JSON.stringify(obj, null, 2); // 보기 좋게 들여쓰기 2칸
const blob = new Blob([json], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click(); // 가짜 클릭 → 다운로드 시작
document.body.removeChild(a);
URL.revokeObjectURL(url); // 메모리 해제
}
function downloadProtocolJson(obj, filename = 'protocolConfig.json') {
const json = JSON.stringify(obj, null, 2); // 보기 좋게 들여쓰기 2칸
const blob = new Blob([json], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/* (A) 고정 data 객체 그대로 저장 */
document.getElementById('downloadDeviceBtn')?.addEventListener('click', () => {
downloadDeviceJson(collectData(data));
});
document.getElementById('downloadProtocolBtn')?.addEventListener('click', () => {
downloadProtocolJson(collectConfigData(configData));
})
function showPage(pageName) {
Object.values(pages).forEach(p => p.style.display = 'none');
pages[pageName].style.display = 'block';
// 버튼 텍스트를 페이지 제목으로 설정
const btn = document.querySelector(`.nav-items button[data-page="${pageName}"]`);
if (btn) {
titleEl.textContent = btn.textContent;
} else {
titleEl.textContent = pageName.charAt(0).toUpperCase() + pageName.slice(1); // 기본값
}
// 페이지 전환 시 항상 마지막으로 로드된 전체 데이터를 UI에 반영
// if (lastLoadedRegisterData && Object.keys(lastLoadedRegisterData).length > 0) {
// loadFromRegisterJson(lastLoadedRegisterData);
// }
toggleNetInputs(); // Network 페이지 로드 시 IP 모드에 따른 입력 필드 활성화/비활성화
}
document.querySelectorAll('.nav-items button').forEach(btn => {
btn.addEventListener('click', () => {
// Object.values(pages).forEach(p=>p.style.display='none');
// pages[btn.dataset.page].style.display='block';
// titleEl.textContent = btn.textContent;
showPage(btn.dataset.page);
});
});
const ssidList = document.getElementById('ssid-list');
const ssidTmpl = document.getElementById('ssid-template');
function createSsidRow(obj = {wifi_ssid: '', wifi_password: '', wifi_security: 'none'}) {
const node = ssidTmpl.content.firstElementChild.cloneNode(true);
node.querySelector('.ssid-name').value = obj.wifi_ssid ?? '';
node.querySelector('.ssid-pw').value = obj.wifi_password ?? '';
node.querySelector('select').value = (obj.wifi_security ?? 'none').toLowerCase();
return node;
}
function addSsidRow() {
ssidList.appendChild(createSsidRow());
}
document.getElementById('add-ssid').addEventListener('click', addSsidRow);
1 year ago
document.getElementById('delete-ssid').addEventListener('click', () => {
if (ssidList.children.length > 1) {
1 year ago
ssidList.removeChild(ssidList.lastElementChild);
} else if (ssidList.children.length == 1) {
1 year ago
const firstRow = ssidList.firstElementChild;
firstRow.querySelector('.ssid-name').value = '';
firstRow.querySelector('.ssid-pw').value = '';
firstRow.querySelector('select').value = 'none';
}
})
ssidList.addEventListener('click', e => {
if (e.target.classList.contains('toggle-pw')) {
const pw = e.target.closest('.ssid-row').querySelector('.ssid-pw');
pw.type = pw.type === 'password' ? 'text' : 'password';
e.target.textContent = pw.type === 'password' ? 'show' : 'hide';
} else if (e.target.dataset.action === 'delete-ssid') {
1 year ago
const ssidRow = e.target.closest('.ssid-row');
if (ssidRow) {
1 year ago
removeSsidRow(ssidRow);
}
}
});
function gatherSsidRows() {
const arr = [];
ssidList.querySelectorAll('.ssid-row').forEach(row => {
const ssid = row.querySelector('.ssid-name').value.trim();
const pw = row.querySelector('.ssid-pw').value.trim();
const sec = row.querySelector('select').value;
if (ssid) arr.push({wifi_ssid: ssid, wifi_password: pw, wifi_security: sec});
});
return arr.length ? arr : [{wifi_ssid: '', wifi_password: '', wifi_security: 'none'}];
}
/* ───────────── 4. DHCP ↔ Static 전환 입력 잠금 ───────────── */
const netFields = ['wifi_ip', 'wifi_netmask', 'wifi_gateway', 'wifi_dns1', 'wifi_dns2']
.map(id => document.getElementById(id));
function toggleNetInputs() {
const isStatic = document.querySelector('input[name="ipMode"]:checked')?.value === 'static';
netFields.forEach(el => {
el.disabled = !isStatic;
el.classList.toggle('disabled', !isStatic);
});
}
document.querySelectorAll('input[name="ipMode"]').forEach(r => r.addEventListener('change', toggleNetInputs));
function axisValueToPair(val) {
return val.startsWith('-')
? {axis: val.slice(1), sign: 'minus'}
: {axis: val, sign: 'plus'};
}
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;
/* 일반 input/select */
for (const k in obj) {
const el = document.getElementById(k);
if (el) el.value = obj[k] ?? '';
}
/* IMU select 값 재구성 */
['x', 'y', 'z'].forEach(a => {
const axis = obj[`imu_remap_${a}`] || 'x';
const sign = obj[`imu_remap_${a}_sign`] || 'plus';
document.getElementById(`imu_remap_${a}`).value = pairToAxisValue(axis, sign);
});
/* SSID */
ssidList.innerHTML = '';
(obj.WIFI_SSID || []).forEach(o => ssidList.appendChild(createSsidRow(o)));
if (!ssidList.children.length) addSsidRow();
toggleNetInputs(); // 초기 잠금 상태
}
loadFromData(data);
let regProtocolSelect;
function toggleProtocolMenus() {
if (!regProtocolSelect) return;
const selectProtocol = regProtocolSelect.value;
const modbusSidebarBtn = document.getElementById('modbusSidebarBtn');
const opcUaSidebarBtn = document.getElementById('opcUaSidebarBtn');
const canSidebarBtn = document.getElementById('canSidebarBtn');
if (modbusSidebarBtn) modbusSidebarBtn.style.display = 'none';
if (opcUaSidebarBtn) opcUaSidebarBtn.style.display = 'none';
if (canSidebarBtn) canSidebarBtn.style.display = 'none';
if (selectProtocol === 'MODBUS') {
if (modbusSidebarBtn) modbusSidebarBtn.style.display = 'block';
} else if (selectProtocol === 'OPC_UA') {
if (opcUaSidebarBtn) opcUaSidebarBtn.style.display = 'block';
}
// CAN Input이 on일 때만 CAN 메뉴 보이기
const canInputEl = document.getElementById('reg_can_input');
if (canInputEl && canInputEl.value === 'on') {
if (canSidebarBtn) canSidebarBtn.style.display = 'block';
}
// Protocol configuration 메뉴 자동 펼치기
const protocolDetails = document.querySelectorAll('details.nav')[1];
if (protocolDetails) protocolDetails.open = true;
}
function loadFromRegisterJson(register) { // register.json 데이터 로드
if (!register) {
console.error("Register data is null or undefined.");
return;
}
lastLoadedRegisterData = {...register};
console.log("Extracted Body Data:", lastLoadedRegisterData); // DTO 객체 로깅
// 1. configIdMap을 순회하며 DTO의 camelCase 필드명과 HTML ID 매핑을 사용하여 UI 업데이트
for (const htmlId in configIdMap) {
const dtoKey = configIdMap[htmlId]; // DTO의 camelCase 필드명
const el = document.getElementById(htmlId);
if (el && lastLoadedRegisterData.hasOwnProperty(dtoKey)) {
const value = lastLoadedRegisterData[dtoKey];
if (el.type === 'checkbox') {
// AnalogPort Enum은 "0", "1" 문자열 또는 boolean으로 파싱될 수 있습니다.
// UI 체크박스는 boolean 값을 필요로 하므로, "1"이면 체크, "0"이면 체크 해제.
el.checked = (value === "1" || value === true);
} else {
// 기타 입력 필드 (select, text input)
// null 또는 undefined 값에 대비하여 빈 문자열로 처리
el.value = value !== null && value !== undefined ? value : '';
}
} else if (el) {
// DTO에 해당 필드가 없으면 UI 요소 초기화 (선택 사항)
if (el.type === 'checkbox') {
el.checked = false;
} else {
el.value = '';
}
console.warn(`DTO field '${dtoKey}' (mapped from HTML ID '${htmlId}') not found in loaded data.`);
} else {
console.warn(`HTML element with ID '${htmlId}' not found for DTO key '${dtoKey}'.`);
}
}
1 year ago
// protocol 값이 있으면 select 값을 변경 (데이터 로드 시에만)
if (register.protocol && regProtocolSelect) {
regProtocolSelect.value = register.protocol;
}
// 프로토콜 값에 따라 사이드바 버튼 토글
toggleProtocolMenus();
1 year ago
// MODBUS UI 테이블 렌더링
const modbusFieldList = document.getElementById('modbus-field-list');
const modbusRowTemplate = document.getElementById('modbus-row-template');
if (modbusFieldList && modbusRowTemplate) {
modbusFieldList.innerHTML = '';
if (Array.isArray(register.MODBUS) && register.MODBUS.length > 0) {
register.MODBUS.forEach(field => {
const newRow = modbusRowTemplate.content.cloneNode(true);
newRow.querySelector('.field-display').textContent = field.field ?? '';
newRow.querySelector('.addr-display').textContent = field.addr ?? '';
newRow.querySelector('.idt-display').textContent = field.idt ?? '';
newRow.querySelector('.odt-display').textContent = field.odt ?? '';
newRow.querySelector('.dv-display').textContent = field.dv ?? '';
newRow.querySelector('.shift-display').textContent = field.shift ?? '';
newRow.querySelector('.expr-display').textContent = field.expr ?? '';
newRow.querySelector('.mask-display').textContent = field.mask ?? '';
modbusFieldList.appendChild(newRow);
});
} else {
1 year ago
// MODBUS 데이터가 없으면 빈 행 하나 추가
const newRow = modbusRowTemplate.content.cloneNode(true);
modbusFieldList.appendChild(newRow);
}
}
// OPC_UA UI 테이블 렌더링
const opcUaFieldList = document.getElementById('opcua-field-list');
const opcUaRowTemplate = document.getElementById('opcua-row-template');
if (opcUaFieldList && opcUaRowTemplate) {
opcUaFieldList.innerHTML = '';
if (Array.isArray(register.OPC_UA) && register.OPC_UA.length > 0) {
register.OPC_UA.forEach(field => {
const newRow = opcUaRowTemplate.content.cloneNode(true);
newRow.querySelector('.opcua-field-display').textContent = field.field ?? '';
newRow.querySelector('.opcua-addr-display').textContent = field.addr ?? '';
newRow.querySelector('.opcua-ns-display').textContent = field.idt ?? '';
newRow.querySelector('.opcua-odt-display').textContent = field.odt ?? '';
newRow.querySelector('.opcua-dv-display').textContent = field.dv ?? '';
newRow.querySelector('.opcua-shift-display').textContent = field.shift ?? '';
newRow.querySelector('.opcua-expr-display').textContent = field.expr ?? '';
newRow.querySelector('.opcua-mask-display').textContent = field.mask ?? '';
opcUaFieldList.appendChild(newRow);
});
} else {
// OPC_UA 데이터가 없으면 빈 행 하나 추가
const newRow = opcUaRowTemplate.content.cloneNode(true);
opcUaFieldList.appendChild(newRow);
}
}
// CAN 테이블 렌더링
const canFieldList = document.getElementById('can-field-list');
const canRowTemplate = document.getElementById('can-row-template');
canFieldList.innerHTML = '';
if (Array.isArray(register.CAN) && register.CAN.length > 0) {
register.CAN.forEach(field => {
const newRow = canRowTemplate.content.cloneNode(true);
newRow.querySelector('.can-field-display').textContent = field.field ?? '';
newRow.querySelector('.can-id-display').textContent = field.id ?? '';
newRow.querySelector('.can-odt-display').textContent = field.odt ?? '';
newRow.querySelector('.can-dv-display').textContent = field.dv ?? '';
newRow.querySelector('.can-shift-display').textContent = field.shift ?? '';
newRow.querySelector('.can-expr-display').textContent = field.expr ?? '';
newRow.querySelector('.can-mask-display').textContent = field.mask ?? '';
canFieldList.appendChild(newRow);
});
} else {
const newRow = canRowTemplate.content.cloneNode(true);
canFieldList.appendChild(newRow);
}
}
function collectData() {
1 year ago
const res = {...data};
1 year ago
res.wifi_static = document.querySelector('input[name="ipMode"]:checked')?.value === 'static'
? 'on' : 'off';
1 year ago
/* 일반 input/select */
for (const k in res) {
const el = document.getElementById(k);
if (el) res[k] = el.value.trim();
}
1 year ago
/* IMU select → 축·부호 분리 */
['x', 'y', 'z'].forEach(a => {
const {axis, sign} = axisValueToPair(document.getElementById(`imu_remap_${a}`).value);
res[`imu_remap_${a}`] = axis;
res[`imu_remap_${a}_sign`] = sign;
});
1 year ago
/* SSID 배열 */
res.WIFI_SSID = gatherSsidRows();
return res;
}
1 year ago
function collectConfigData() {
const collectedRegister = {...lastLoadedRegisterData};
1 year ago
// configIdMap을 순회하며 HTML ID로부터 값을 가져와 DTO 필드명에 매핑
for (const htmlId in configIdMap) {
const dtoKey = configIdMap[htmlId];
1 year ago
const el = document.getElementById(htmlId);
if (el) {
collectedRegister[dtoKey] = el.value !== null && el.value !== undefined ? el.value.trim() : '';
}
1 year ago
}
// 체크박스 필드 (HTML ID와 DTO 필드명이 같음)
['ai0', 'ai1', 'ai2', 'ai3', 'di0', 'di1', 'di2', 'di3'].forEach(id => {
const el = document.getElementById(id);
if (el && el.type === 'checkbox') {
collectedRegister[id] = el.checked ? "1" : "0";
} else {
if (collectedRegister[id] === undefined) {
collectedRegister[id] = "0";
}
1 year ago
}
});
// MODBUS 테이블 수집 (빈 값은 필드에서 제외)
const modbusRows = document.querySelectorAll('#modbus-field-list tr');
collectedRegister.MODBUS = [];
modbusRows.forEach(row => {
const field = row.querySelector('.field-display')?.textContent.trim();
if (field) {
const obj = {};
obj.field = field;
const addr = row.querySelector('.addr-display')?.textContent.trim();
const idt = row.querySelector('.idt-display')?.textContent.trim();
const odt = row.querySelector('.odt-display')?.textContent.trim();
const dv = row.querySelector('.dv-display')?.textContent.trim();
const shift = row.querySelector('.shift-display')?.textContent.trim();
const expr = row.querySelector('.expr-display')?.textContent.trim();
const mask = row.querySelector('.mask-display')?.textContent.trim();
if (addr) obj.addr = addr;
if (idt) obj.idt = idt;
if (odt) obj.odt = odt;
if (dv) obj.dv = dv;
if (shift) obj.shift = shift;
if (expr) obj.expr = expr;
if (mask) obj.mask = mask;
collectedRegister.MODBUS.push(obj);
}
});
// OPC_UA 테이블 수집 (빈 값은 필드에서 제외)
const opcuaRows = document.querySelectorAll('#opcua-field-list tr');
collectedRegister.OPC_UA = [];
opcuaRows.forEach(row => {
const field = row.querySelector('.opcua-field-display')?.textContent.trim();
if (field) {
const obj = {};
obj.field = field;
const addr = row.querySelector('.opcua-addr-display')?.textContent.trim();
const ns = row.querySelector('.opcua-ns-display')?.textContent.trim();
const odt = row.querySelector('.opcua-odt-display')?.textContent.trim();
const dv = row.querySelector('.opcua-dv-display')?.textContent.trim();
const shift = row.querySelector('.opcua-shift-display')?.textContent.trim();
const expr = row.querySelector('.opcua-expr-display')?.textContent.trim();
const mask = row.querySelector('.opcua-mask-display')?.textContent.trim();
if (addr) obj.addr = addr;
if (ns) obj.ns = ns;
if (odt) obj.odt = odt;
if (dv) obj.dv = dv;
if (shift) obj.shift = shift;
if (expr) obj.expr = expr;
if (mask) obj.mask = mask;
collectedRegister.OPC_UA.push(obj);
}
});
// CAN 테이블 수집 (빈 값은 필드에서 제외)
const canRows = document.querySelectorAll('#can-field-list tr');
collectedRegister.CAN = [];
canRows.forEach(row => {
const field = row.querySelector('.can-field-display')?.textContent.trim();
if (field) {
const obj = {};
obj.field = field;
const id = row.querySelector('.can-id-display')?.textContent.trim();
const odt = row.querySelector('.can-odt-display')?.textContent.trim();
const dv = row.querySelector('.can-dv-display')?.textContent.trim();
const shift = row.querySelector('.can-shift-display')?.textContent.trim();
const expr = row.querySelector('.can-expr-display')?.textContent.trim();
const mask = row.querySelector('.can-mask-display')?.textContent.trim();
if (id) obj.id = id;
if (odt) obj.odt = odt;
if (dv) obj.dv = dv;
if (shift) obj.shift = shift;
if (expr) obj.expr = expr;
if (mask) obj.mask = mask;
collectedRegister.CAN.push(obj);
}
});
1 year ago
return collectedRegister;
}
document.addEventListener('DOMContentLoaded', () => {
const list = document.getElementById('modbus-field-list');
// 처음에 행이 없으면 빈 행을 하나 추가
if (list.children.length === 0) {
1 year ago
const modbusRowTemplate = document.getElementById('modbus-row-template');
if (modbusRowTemplate) {
1 year ago
const newRow = modbusRowTemplate.content.cloneNode(true);
list.appendChild(newRow);
}
}
let selectedRow = null;
document.getElementById('modbus-field-list').addEventListener('click', function (e) {
1 year ago
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('modbus_field_input').value = tr.querySelector('.field-display').textContent;
document.getElementById('modbus_addr_input').value = tr.querySelector('.addr-display').textContent;
document.getElementById('modbus_idt_input').value = tr.querySelector('.idt-display').textContent;
document.getElementById('modbus_odt_input').value = tr.querySelector('.odt-display').textContent;
document.getElementById('modbus_dv_input').value = tr.querySelector('.dv-display').textContent;
document.getElementById('modbus_shift_input').value = tr.querySelector('.shift-display').textContent;
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);
}
}
document.getElementById('opcua-field-list').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('opcua_field_input').value = tr.querySelector('.opcua-field-display').textContent;
document.getElementById('opcua_addr_input').value = tr.querySelector('.opcua-addr-display').textContent;
document.getElementById('opcua_ns_input').value = tr.querySelector('.opcua-ns-display').textContent;
document.getElementById('opcua_odt_input').value = tr.querySelector('.opcua-odt-display').textContent;
document.getElementById('opcua_dv_input').value = tr.querySelector('.opcua-dv-display').textContent;
document.getElementById('opcua_shift_input').value = tr.querySelector('.opcua-shift-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;
});
1 year ago
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', () => {
1 year ago
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_modify_btn').onclick = function () {
1 year ago
if (!selectedRow) return alert('수정할 행을 선택하세요!');
selectedRow.querySelector('.field-display').textContent = document.getElementById('modbus_field_input').value;
selectedRow.querySelector('.addr-display').textContent = document.getElementById('modbus_addr_input').value;
selectedRow.querySelector('.idt-display').textContent = document.getElementById('modbus_idt_input').value;
selectedRow.querySelector('.odt-display').textContent = document.getElementById('modbus_odt_input').value;
selectedRow.querySelector('.dv-display').textContent = document.getElementById('modbus_dv_input').value;
selectedRow.querySelector('.shift-display').textContent = document.getElementById('modbus_shift_input').value;
selectedRow.querySelector('.expr-display').textContent = document.getElementById('modbus_expr_input').value;
selectedRow.querySelector('.mask-display').textContent = document.getElementById('modbus_mask_input').value;
};
// 삭제 버튼
document.getElementById('modbus_delete_btn').onclick = function () {
1 year ago
if (!selectedRow) return alert('삭제할 행을 선택하세요!');
const list = document.getElementById('modbus-field-list');
selectedRow.remove();
selectedRow = null;
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);
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('수정할 행을 선택하세요!');
selectedRow.querySelector('.opcua-field-display').textContent = document.getElementById('opcua_field_input').value;
selectedRow.querySelector('.opcua-addr-display').textContent = document.getElementById('opcua_addr_input').value;
selectedRow.querySelector('.opcua-ns-display').textContent = document.getElementById('opcua_ns_input').value;
selectedRow.querySelector('.opcua-odt-display').textContent = document.getElementById('opcua_odt_input').value;
selectedRow.querySelector('.opcua-dv-display').textContent = document.getElementById('opcua_dv_input').value;
selectedRow.querySelector('.opcua-shift-display').textContent = document.getElementById('opcua_shift_input').value;
selectedRow.querySelector('.opcua-expr-display').textContent = document.getElementById('opcua_expr_input').value;
selectedRow.querySelector('.opcua-mask-display').textContent = document.getElementById('opcua_mask_input').value;
};
// 삭제 버튼
document.getElementById('opcua_delete_btn').onclick = function () {
if (!selectedRow) return alert('삭제할 행을 선택하세요!');
const list = document.getElementById('opcua-field-list');
selectedRow.remove();
selectedRow = null;
document.querySelectorAll('.opcua-input-area input').forEach(i => i.value = '');
};
// CAN 테이블
document.getElementById('can_add_btn').addEventListener('click', () => {
const field = document.getElementById('can_field_input').value;
const id = document.getElementById('can_id_input').value;
const odt = document.getElementById('can_odt_input').value;
const dv = document.getElementById('can_dv_input').value;
const shift = document.getElementById('can_shift_input').value;
const expr = document.getElementById('can_expr_input').value;
const mask = document.getElementById('can_mask_input').value;
const canRowTemplate = document.getElementById('can-row-template');
const newRow = canRowTemplate.content.cloneNode(true);
newRow.querySelector('.can-field-display').textContent = field;
newRow.querySelector('.can-id-display').textContent = id;
newRow.querySelector('.can-odt-display').textContent = odt;
newRow.querySelector('.can-dv-display').textContent = dv;
newRow.querySelector('.can-shift-display').textContent = shift;
newRow.querySelector('.can-expr-display').textContent = expr;
newRow.querySelector('.can-mask-display').textContent = mask;
canFieldList.appendChild(newRow);
document.getElementById('can_field_input').value = '';
document.getElementById('can_id_input').value = '';
document.getElementById('can_odt_input').value = '';
document.getElementById('can_dv_input').value = '';
document.getElementById('can_shift_input').value = '';
document.getElementById('can_expr_input').value = '';
document.getElementById('can_mask_input').value = '';
});
document.getElementById('can_modify_btn').onclick = function () {
if (!selectedRow) return alert('수정할 행을 선택하세요!');
selectedRow.querySelector('.can-field-display').textContent = document.getElementById('can_field_input').value;
selectedRow.querySelector('.can-id-display').textContent = document.getElementById('can_id_input').value;
selectedRow.querySelector('.can-odt-display').textContent = document.getElementById('can_odt_input').value;
selectedRow.querySelector('.can-dv-display').textContent = document.getElementById('can_dv_input').value;
selectedRow.querySelector('.can-shift-display').textContent = document.getElementById('can_shift_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;
};
document.getElementById('can_delete_btn').onclick = function () {
if (!selectedRow) return alert('삭제할 행을 선택하세요!');
const list = document.getElementById('can-field-list');
selectedRow.remove();
selectedRow = null;
document.querySelectorAll('.can-input-area input').forEach(i => i.value = '');
};
1 year ago
regProtocolSelect = document.getElementById('reg_protocol');
if (regProtocolSelect) {
regProtocolSelect.addEventListener('change', () => {
console.log('regProtocolSelect 변경 감지', regProtocolSelect.value);
toggleProtocolMenus();
});
toggleProtocolMenus();
}
document.getElementById('loadDeviceBtn_ssid')?.addEventListener('click', () => {
fetch('http://localhost:8080/setting/get-device')
.then(res => {
if (!res.ok) {
console.error(`HTTP error! status: ${res.status}`);
throw new Error('Device file not found or server error');
}
return res.json();
})
.then(({body}) => {
loadFromData(body);
alert('Device 설정 파일 로드 완료');
})
.catch(error => {
console.error('Device 설정 파일 로드 중 오류 발생:', error);
alert('Device 설정 파일 로드 실패! 기본값으로 시작합니다.');
loadFromData(data); // 오류 발생 시 data의 초기값으로 UI 초기화
});
});
document.getElementById('loadDeviceBtn_network')?.addEventListener('click', () => {
fetch('http://localhost:8080/setting/get-device')
.then(res => {
if (!res.ok) {
console.error(`HTTP error! status: ${res.status}`);
throw new Error('Device file not found or server error');
}
return res.json();
})
.then(({body}) => {
loadFromData(body);
alert('Device 설정 파일 로드 완료');
})
.catch(error => {
console.error('Device 설정 파일 로드 중 오류 발생:', error);
alert('Device 설정 파일 로드 실패! 기본값으로 시작합니다.');
loadFromData(data); // 오류 발생 시 data의 초기값으로 UI 초기화
});
});
document.getElementById('loadDeviceBtn_io')?.addEventListener('click', () => {
fetch('http://localhost:8080/setting/get-device')
.then(res => {
if (!res.ok) {
console.error(`HTTP error! status: ${res.status}`);
throw new Error('Device file not found or server error');
}
return res.json();
})
.then(deviceJsonData => {
loadFromData(deviceJsonData);
alert('Device 설정 파일 로드 완료');
})
.catch(error => {
console.error('Device 설정 파일 로드 중 오류 발생:', error);
alert('Device 설정 파일 로드 실패! 기본값으로 시작합니다.');
loadFromData(data); // 오류 발생 시 data의 초기값으로 UI 초기화
});
});
document.getElementById('saveDeviceBtn_ssid').addEventListener('click', () => {
fetch('http://localhost:8080/setting/device', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectData())
})
.then(res => {
if (res.ok) {
alert('Device config successfully');
} else {
alert('Device config failed: ' + res.statusText);
console.error('Device config failed:', res); // 응답 객체 자체를 로깅
}
})
.catch(error => {
console.error('Device config failed::', error); // 네트워크 오류 로깅
alert('Device config failed:');
});
});
document.getElementById('saveDeviceBtn_network').addEventListener('click', () => {
fetch('http://localhost:8080/setting/device', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectData())
})
.then(res => {
if (res.ok) {
alert('Device config successfully');
} else {
alert('Device config failed: ' + res.statusText);
console.error('Device config failed:', res); // 응답 객체 자체를 로깅
}
})
.catch(error => {
console.error('Device config failed::', error); // 네트워크 오류 로깅
alert('Device config failed:');
});
});
document.getElementById('saveDeviceBtn_io').addEventListener('click', () => {
fetch('http://localhost:8080/setting/device', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectData())
})
.then(res => {
if (res.ok) {
alert('Device config successfully');
} else {
alert('Device config failed: ' + res.statusText);
console.error('Device config failed:', res); // 응답 객체 자체를 로깅
}
})
.catch(error => {
console.error('Device config failed::', error); // 네트워크 오류 로깅
alert('Device config failed:');
});
});
document.getElementById('loadProtocolBtn').addEventListener('click', () => {
console.log('버튼 클릭 됨');
fetch('http://localhost:8080/setting/get-register')
.then(res => {
if (!res.ok) {
console.error(`HTTP error! status: ${res.status}`);
throw new Error('Register file not found or server error');
}
return res.json();
})
.then(apiResponseData => {
if (apiResponseData && apiResponseData.body) {
loadFromRegisterJson(apiResponseData.body);
alert('Protocol 설정 파일 로드 완료');
} else {
console.error('API 응답에 body 필드가 없습니다.', apiResponseData);
alert('Protocol 설정 파일 로드 실패! 유효한 응답 형식이 아닙니다.');
loadFromRegisterJson(configData);
}
})
.catch(error => {
console.error('Protocol 설정 파일 로드 중 오류 발생:', error);
alert('Protocol 설정 파일 로드 실패! ' + error.message + ' 기본값으로 시작합니다.');
loadFromRegisterJson(configData); // 오류 발생 시 configData의 초기값으로 UI 초기화
});
});
document.getElementById('loadModbusBtn').addEventListener('click', () => {
console.log('버튼 클릭 됨');
fetch('http://localhost:8080/setting/get-register')
.then(res => {
if (!res.ok) {
console.error(`HTTP error! status: ${res.status}`);
throw new Error('Register file not found or server error');
}
return res.json();
})
.then(apiResponseData => {
if (apiResponseData && apiResponseData.body) {
loadFromRegisterJson(apiResponseData.body);
alert('Protocol 설정 파일 로드 완료');
} else {
console.error('API 응답에 body 필드가 없습니다.', apiResponseData);
alert('Protocol 설정 파일 로드 실패! 유효한 응답 형식이 아닙니다.');
loadFromRegisterJson(configData);
}
})
.catch(error => {
console.error('Protocol 설정 파일 로드 중 오류 발생:', error);
alert('Protocol 설정 파일 로드 실패! ' + error.message + ' 기본값으로 시작합니다.');
loadFromRegisterJson(configData); // 오류 발생 시 configData의 초기값으로 UI 초기화
});
});
document.getElementById('loadOpcUaFileBtn').addEventListener('click', () => {
console.log('버튼 클릭 됨');
fetch('http://localhost:8080/setting/get-register')
.then(res => {
if (!res.ok) {
console.error(`HTTP error! status: ${res.status}`);
throw new Error('Register file not found or server error');
}
return res.json();
})
.then(apiResponseData => {
if (apiResponseData && apiResponseData.body) {
loadFromRegisterJson(apiResponseData.body);
alert('Protocol 설정 파일 로드 완료');
} else {
console.error('API 응답에 body 필드가 없습니다.', apiResponseData);
alert('Protocol 설정 파일 로드 실패! 유효한 응답 형식이 아닙니다.');
loadFromRegisterJson(configData);
}
})
.catch(error => {
console.error('Protocol 설정 파일 로드 중 오류 발생:', error);
alert('Protocol 설정 파일 로드 실패! ' + error.message + ' 기본값으로 시작합니다.');
loadFromRegisterJson(configData); // 오류 발생 시 configData의 초기값으로 UI 초기화
});
});
document.getElementById('saveProtocolBtn').addEventListener('click', () => {
console.log('버튼 클릭 됨');
console.log(collectConfigData());
fetch('http://localhost:8080/setting/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectConfigData())
})
.then(res => {
if (res.ok) {
alert('Register 설정 전송 완료');
} else {
alert('Register 설정 전송 실패: ' + res.statusText);
console.error('Register 설정 전송 실패:', res); // 응답 객체 자체를 로깅
}
})
.catch(error => {
console.error('Register 설정 전송 중 오류 발생:', error); // 네트워크 오류 로깅
alert('Register 설정 전송 중 통신 오류 발생');
});
});
document.getElementById('saveModbusBtn').addEventListener('click', () => {
console.log('버튼 클릭 됨');
fetch('http://localhost:8080/setting/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectConfigData())
})
.then(res => {
if (res.ok) {
alert('Register 설정 전송 완료');
} else {
alert('Register 설정 전송 실패: ' + res.statusText);
console.error('Register 설정 전송 실패:', res); // 응답 객체 자체를 로깅
}
})
.catch(error => {
console.error('Register 설정 전송 중 오류 발생:', error); // 네트워크 오류 로깅
alert('Register 설정 전송 중 통신 오류 발생');
});
});
document.getElementById('saveOpcUaFileBtn').addEventListener('click', () => {
console.log('버튼 클릭 됨');
fetch('http://localhost:8080/setting/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectConfigData())
})
.then(res => {
if (res.ok) {
alert('Register 설정 전송 완료');
} else {
alert('Register 설정 전송 실패: ' + res.statusText);
console.error('Register 설정 전송 실패:', res); // 응답 객체 자체를 로깅
}
})
.catch(error => {
console.error('Register 설정 전송 중 오류 발생:', error); // 네트워크 오류 로깅
alert('Register 설정 전송 중 통신 오류 발생');
});
});
document.getElementById('reg_can_input')?.addEventListener('change', toggleProtocolMenus);//--------------------------------
// DOMContentLoaded 내부에 추가: CAN 테이블도 처음에 빈 행이 없으면 하나 추가
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.getElementById('loadCanBtn').addEventListener('click', () => {
fetch('http://localhost:8080/setting/get-register')
.then(res => res.json())
.then(apiResponseData => {
if (apiResponseData && apiResponseData.body) {
loadFromRegisterJson(apiResponseData.body);
alert('Protocol 설정 파일 로드 완료');
}
});
});
document.getElementById('saveCanBtn').addEventListener('click', () => {
fetch('http://localhost:8080/setting/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectConfigData())
})
.then(res => {
if (res.ok) {
alert('Register 설정 전송 완료');
} else {
alert('Register 설정 전송 실패: ' + res.statusText);
}
});
});
document.getElementById('uploadProtocolBtn').addEventListener('click', () => {
fetch('http://localhost:8080/setting/register-upload', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectConfigData())
})
.then(res => res.json())
.then(data => {
// result_code가 200이면 성공
if (data.result && data.result.result_code === 200) {
alert('하드웨어 업로드 성공!');
} else {
alert('하드웨어 업로드 실패: ' + (data.result?.result_message || data.body || ''));
}
})
.catch(err => {
alert('통신 오류: ' + err.message);
});
});
document.getElementById('uploadModbusBtn').addEventListener('click', () => {
fetch('http://localhost:8080/setting/register-upload', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectConfigData())
})
.then(res => res.json())
.then(data => {
// result_code가 200이면 성공
if (data.result && data.result.result_code === 200) {
alert('하드웨어 업로드 성공!');
} else {
alert('하드웨어 업로드 실패: ' + (data.result?.result_message || data.body || ''));
}
})
.catch(err => {
alert('통신 오류: ' + err.message);
});
});
document.getElementById('uploadOpcuaFileBtn').addEventListener('click', () => {
fetch('http://localhost:8080/setting/register-upload', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectConfigData())
})
.then(res => res.json())
.then(data => {
// result_code가 200이면 성공
if (data.result && data.result.result_code === 200) {
alert('하드웨어 업로드 성공!');
} else {
alert('하드웨어 업로드 실패: ' + (data.result?.result_message || data.body || ''));
}
})
.catch(err => {
alert('통신 오류: ' + err.message);
});
});
document.getElementById('uploadCanBtn').addEventListener('click', () => {
fetch('http://localhost:8080/setting/register-upload', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(collectConfigData())
})
.then(res => res.json())
.then(data => {
// result_code가 200이면 성공
if (data.result && data.result.result_code === 200) {
alert('하드웨어 업로드 성공!');
} else {
alert('하드웨어 업로드 실패: ' + (data.result?.result_message || data.body || ''));
}
})
.catch(err => {
alert('통신 오류: ' + err.message);
});
});
</script>
</body>
</html>