초기화
This commit is contained in:
@@ -0,0 +1,728 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>설비 관리 및 실시간 HMI - ChemiFactory</title>
|
||||
<!-- Google Fonts Inter -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<!-- 테마 적용 (base.css보다 먼저 로드해 FOWT 방지) -->
|
||||
<script src="js/theme.js"></script>
|
||||
<link rel="stylesheet" href="css/base.css">
|
||||
<style>
|
||||
/* 설비 카드 그리드 전용 스타일 */
|
||||
.equip-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.equip-card {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 180px;
|
||||
}
|
||||
.equip-card.active-card {
|
||||
border-color: var(--accent-blue);
|
||||
box-shadow: 0 0 15px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
.equip-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.equip-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.equip-type {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.equip-status-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 24px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* HMI 디테일 분할 레이아웃 */
|
||||
.hmi-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 2fr;
|
||||
gap: 24px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.hmi-section {
|
||||
background-color: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
}
|
||||
.hmi-panel-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 실시간 램프 그리드 */
|
||||
.lamp-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.lamp-card {
|
||||
background: var(--overlay-subtle);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.lamp-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.lamp-indicator {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--toggle-off-bg); /* OFF 상태 */
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.lamp-indicator.active {
|
||||
background-color: var(--accent-green);
|
||||
box-shadow: 0 0 10px var(--accent-green-glow);
|
||||
}
|
||||
|
||||
/* 제어 버튼 폼 */
|
||||
.control-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#hmi-detail-panel {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.initialization-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--modal-backdrop);
|
||||
backdrop-filter: blur(2px);
|
||||
border-radius: 10px;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.initialization-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.initialization-overlay-message {
|
||||
padding: 22px 30px;
|
||||
border: 1px solid rgba(59, 130, 246, 0.65);
|
||||
border-radius: 10px;
|
||||
background: var(--modal-bg);
|
||||
color: var(--text-primary);
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
line-height: 1.7;
|
||||
box-shadow: 0 12px 30px var(--card-shadow);
|
||||
}
|
||||
.btn-control {
|
||||
padding: 14px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--overlay-subtle);
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.btn-control:hover {
|
||||
background: var(--overlay-strong);
|
||||
border-color: var(--text-secondary);
|
||||
}
|
||||
.btn-control:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.btn-control.momentary-active {
|
||||
background: rgba(59, 130, 246, 0.2) !important;
|
||||
border-color: var(--accent-blue) !important;
|
||||
box-shadow: 0 0 10px var(--accent-blue-glow);
|
||||
}
|
||||
|
||||
/* D영역 수치 입출력 */
|
||||
.register-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
.register-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: var(--overlay-subtle);
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.register-value {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.lamp-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.input-inline {
|
||||
background: var(--bg-dark);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
.input-inline:focus {
|
||||
border-color: var(--accent-blue);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 모달 스타일 */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--modal-backdrop);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.modal-content {
|
||||
background-color: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
width: 90%;
|
||||
max-width: 450px;
|
||||
box-shadow: 0 10px 30px var(--card-shadow);
|
||||
}
|
||||
.modal-header {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.form-control {
|
||||
width: 100%;
|
||||
background-color: var(--bg-dark);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: var(--accent-blue);
|
||||
outline: none;
|
||||
}
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
/* HMI Functional Groups Styling */
|
||||
.group-tab-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
background: var(--overlay-subtle);
|
||||
padding: 6px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
width: fit-content;
|
||||
}
|
||||
.btn-tab {
|
||||
padding: 8px 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-tab.active {
|
||||
background: var(--accent-blue);
|
||||
color: var(--text-on-accent);
|
||||
box-shadow: 0 0 10px var(--accent-blue-glow);
|
||||
}
|
||||
|
||||
.hmi-groups-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hmi-group-card {
|
||||
background-color: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.hmi-group-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* HMI Button with Integrated Status Glow */
|
||||
.btn-hmi {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 14px;
|
||||
background: var(--overlay-subtle);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-hmi:hover {
|
||||
background: var(--overlay-strong);
|
||||
}
|
||||
.btn-hmi:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
/* HMI Lamp Base (Global) */
|
||||
.hmi-lamp {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--toggle-off-bg);
|
||||
box-shadow: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.hmi-lamp.active {
|
||||
background-color: var(--accent-green);
|
||||
box-shadow: 0 0 8px var(--accent-green-glow);
|
||||
}
|
||||
.hmi-lamp.active-red {
|
||||
background-color: var(--accent-red);
|
||||
box-shadow: 0 0 8px var(--accent-red-glow);
|
||||
}
|
||||
|
||||
/* HMI Button with Integrated Status Glow */
|
||||
.btn-hmi.active {
|
||||
border-color: var(--accent-green);
|
||||
}
|
||||
.btn-hmi.active .hmi-lamp {
|
||||
background-color: var(--accent-green);
|
||||
box-shadow: 0 0 8px var(--accent-green-glow);
|
||||
}
|
||||
.btn-hmi.active-red {
|
||||
border-color: var(--accent-red);
|
||||
}
|
||||
.btn-hmi.active-red .hmi-lamp {
|
||||
background-color: var(--accent-red);
|
||||
box-shadow: 0 0 8px var(--accent-red-glow);
|
||||
}
|
||||
|
||||
/* Compact Port Hanger Grid */
|
||||
.port-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
.port-cell {
|
||||
padding: 12px 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
background: var(--overlay-subtle);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
.port-cell:hover {
|
||||
background: var(--overlay-strong);
|
||||
border-color: var(--text-secondary);
|
||||
}
|
||||
.port-cell.port-active-auto-down {
|
||||
border-color: var(--accent-blue);
|
||||
color: var(--accent-blue);
|
||||
box-shadow: inset 0 0 8px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
.port-cell.port-active-manual-down {
|
||||
border-color: var(--accent-orange);
|
||||
color: var(--accent-orange);
|
||||
box-shadow: inset 0 0 8px rgba(245, 158, 11, 0.15);
|
||||
}
|
||||
.port-cell.port-active-manual-up {
|
||||
border-color: var(--accent-green);
|
||||
color: var(--accent-green);
|
||||
box-shadow: inset 0 0 8px rgba(16, 185, 129, 0.15);
|
||||
}
|
||||
|
||||
/* Popover Overlay */
|
||||
.hmi-popover {
|
||||
position: absolute;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 10px 25px var(--card-shadow);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
z-index: 500;
|
||||
width: 130px;
|
||||
}
|
||||
.hmi-popover button {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.hmi-groups-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 공통 사이드바 -->
|
||||
<aside class="sidebar" id="sidebar"></aside>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="main-content">
|
||||
<header class="content-header">
|
||||
<div class="page-title">
|
||||
<h1 id="main-page-title">설비 및 실시간 HMI 제어</h1>
|
||||
<p id="main-page-desc">등록된 설비 리스트 확인 및 개별 HMI 원격 제어 패널</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 설비 목록 그리드 -->
|
||||
<div class="equip-grid" id="equipment-cards-container">
|
||||
<!-- 동적 로드 -->
|
||||
</div>
|
||||
|
||||
<!-- HMI 상세 원격 제어 판넬 (설비 선택 시 노출) -->
|
||||
<div id="hmi-detail-panel" style="display: none; margin-top: 15px;">
|
||||
<div id="initialization-overlay" class="initialization-overlay" aria-hidden="true">
|
||||
<div class="initialization-overlay-message">설비 초기화 중<br><span style="font-size:13px; font-weight:400; color:var(--text-secondary);">PLC 상태 동기화가 완료될 때까지 조작할 수 없습니다.</span></div>
|
||||
</div>
|
||||
<!-- A/B 그룹 제어 토글 탭, 목록 복귀 & 수동 초기화 버튼 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; flex-wrap: wrap; gap: 12px;">
|
||||
<div style="display: flex; gap: 12px; align-items: center;">
|
||||
<button class="btn" style="background: var(--overlay-hover); border: 1px solid var(--border-color); color: var(--text-primary); padding: 8px 16px; font-weight: 600; font-size: 14px;" onclick="goBackToEquipmentList()">📋 목록</button>
|
||||
<div class="group-tab-container" style="margin-bottom: 0;">
|
||||
<button class="btn-tab active" id="btn-tab-a" onclick="setHmiActiveGroup('A')">A그룹</button>
|
||||
<button class="btn-tab" id="btn-tab-b" onclick="setHmiActiveGroup('B')">B그룹</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn" style="background: var(--nav-active-bg); border: 1px solid var(--accent-blue); color: var(--accent-blue); padding: 8px 16px; font-weight: 600; font-size: 14px;" onclick="triggerManualSync()">🔄 Sync 초기화</button>
|
||||
</div>
|
||||
|
||||
<!-- 6대 기능 그룹 그리드 -->
|
||||
<div class="hmi-groups-container">
|
||||
<!-- 1그룹: 그룹 활성화 및 리셋 -->
|
||||
<div class="hmi-group-card">
|
||||
<div class="hmi-group-title">
|
||||
<span>메인 제어</span>
|
||||
<span class="badge badge-online" id="hmi-websocket-status">실시간 연결</span>
|
||||
</div>
|
||||
<!-- 안전 인터락 상태 모니터링 영역 (A-1) -->
|
||||
<div class="interlock-status-container" style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; padding: 6px; background: var(--overlay-subtle); border-radius: 6px; margin-bottom: 12px; border: 1px solid var(--border-color); text-align: center;">
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
|
||||
<span>비상정지</span>
|
||||
<span class="hmi-lamp" id="lamp-estop"></span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
|
||||
<span>도어(좌)</span>
|
||||
<span class="hmi-lamp" id="lamp-door-left"></span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
|
||||
<span>도어(우)</span>
|
||||
<span class="hmi-lamp" id="lamp-door-right"></span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
|
||||
<span>수위장치</span>
|
||||
<span class="hmi-lamp" id="lamp-water-level"></span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 10px; color: var(--text-secondary);">
|
||||
<span>히터장치</span>
|
||||
<span class="hmi-lamp" id="lamp-heater-interlock"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-grid" style="grid-template-columns: 1fr 1fr; margin-bottom: 0;">
|
||||
<button class="btn-hmi" id="btn-group-active-on" onclick="sendHmiBit(1, 'group_active_on')">전체 활성화 ON <span class="hmi-lamp" id="lamp-group-active-on"></span></button>
|
||||
<button class="btn-hmi" id="btn-group-active-off" onclick="sendHmiBit(2, 'group_active_off')">전체 활성화 OFF <span class="hmi-lamp" id="lamp-group-active-off"></span></button>
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns: repeat(3, 1fr); gap:10px; margin-top:8px;">
|
||||
<button class="btn-hmi btn-danger" style="background: rgba(239,68,68,0.15);" id="btn-group-stop" onclick="sendHmiBit(3, 'group_stop')">전체정지 <span class="hmi-lamp" id="lamp-group-stop"></span></button>
|
||||
<button class="btn-hmi" style="justify-content:center; cursor:default;" id="btn-group-pause">일시정지 <span class="hmi-lamp" style="margin-left:8px;" id="lamp-group-pause"></span></button>
|
||||
<button class="btn-hmi" style="justify-content:center; cursor:default;" id="btn-group-restart">재시작 <span class="hmi-lamp" style="margin-left:8px;" id="lamp-group-restart"></span></button>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-hmi" style="justify-content:center; gap:8px;" id="btn-interlock-reset" onclick="sendHmiBit(0, 'interlock_reset')">⚡ 인터락 리셋 <span class="hmi-lamp" id="lamp-interlock-reset"></span></button>
|
||||
</div>
|
||||
|
||||
<!-- 2그룹: 함침부 작동 -->
|
||||
<div class="hmi-group-card">
|
||||
<div class="hmi-group-title">
|
||||
<span>🧪 함침부</span>
|
||||
<div style="display: flex; gap: 6px;">
|
||||
<button class="btn-hmi" id="btn-impreg-hanger-on-g2" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(4, 'impreg_hanger_on')">ON <span class="hmi-lamp" id="lamp-impreg-hanger-on-g2"></span></button>
|
||||
<button class="btn-hmi" id="btn-impreg-hanger-off-g2" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(5, 'impreg_hanger_off')">OFF <span class="hmi-lamp" id="lamp-impreg-hanger-off-g2"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 기존 조작 버튼들 -->
|
||||
<div style="display:flex; justify-content:space-between; gap:10px;">
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-impreg-auto" onclick="sendHmiBit(24, 'impreg_auto')">자동 모드 <span class="hmi-lamp" id="lamp-impreg-auto"></span></button>
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-impreg-manual" onclick="sendHmiBit(25, 'impreg_manual')">수동 모드 <span class="hmi-lamp" id="lamp-impreg-manual"></span></button>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; gap:10px;">
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-pump-circ" onclick="sendHmiBit(26, 'pump_circ')">순환 펌프 <span class="hmi-lamp" id="lamp-pump-circ"></span></button>
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-pump-drain" onclick="sendHmiBit(27, 'pump_drain')">배출 펌프 <span class="hmi-lamp" id="lamp-pump-drain"></span></button>
|
||||
</div>
|
||||
<div class="register-row">
|
||||
<span class="lamp-label">순환펌프 재작동 시간</span>
|
||||
<span class="register-value" id="val-pump-circ-time">0 <span style="font-size:11px; font-weight:normal; color:var(--text-secondary);">초</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3그룹: 건조기 작동 -->
|
||||
<div class="hmi-group-card">
|
||||
<div class="hmi-group-title">
|
||||
<span>♨️ 건조부</span>
|
||||
<div style="display: flex; gap: 6px;">
|
||||
<button class="btn-hmi" id="btn-dryer-group-on" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(6, 'dryer_on')">ON <span class="hmi-lamp" id="lamp-dryer-on"></span></button>
|
||||
<button class="btn-hmi" id="btn-dryer-group-off" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(7, 'dryer_off')">OFF <span class="hmi-lamp" id="lamp-dryer-off"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 기존 조작 버튼들 -->
|
||||
<div style="display:flex; justify-content:space-between; gap:10px;">
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-dryer-auto" onclick="sendHmiBit(32, 'dryer_auto')">자동 모드 <span class="hmi-lamp" id="lamp-dryer-auto"></span></button>
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-dryer-manual" onclick="sendHmiBit(33, 'dryer_manual')">수동 모드 <span class="hmi-lamp" id="lamp-dryer-manual"></span></button>
|
||||
</div>
|
||||
<button class="btn-hmi" id="btn-dryer-fan" onclick="sendHmiBit(34, 'dryer_fan')">송풍기 작동 <span class="hmi-lamp" id="lamp-dryer-fan"></span></button>
|
||||
|
||||
<div style="display:grid; grid-template-columns: repeat(5, 1fr); gap:6px;">
|
||||
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h1" onclick="sendHmiBit(35, 'dryer_h1')">H1 <span class="hmi-lamp" id="lamp-dryer-h1"></span></button>
|
||||
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h2" onclick="sendHmiBit(36, 'dryer_h2')">H2 <span class="hmi-lamp" id="lamp-dryer-h2"></span></button>
|
||||
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h3" onclick="sendHmiBit(37, 'dryer_h3')">H3 <span class="hmi-lamp" id="lamp-dryer-h3"></span></button>
|
||||
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h4" onclick="sendHmiBit(38, 'dryer_h4')">H4 <span class="hmi-lamp" id="lamp-dryer-h4"></span></button>
|
||||
<button class="btn-hmi" style="flex-direction:column; padding:8px 4px; gap:4px; font-size:11px;" id="btn-dryer-h5" onclick="sendHmiBit(39, 'dryer_h5')">H5 <span class="hmi-lamp" id="lamp-dryer-h5"></span></button>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; gap:10px;">
|
||||
<div class="btn-hmi" style="flex:1; cursor:default; background:var(--overlay-subtle);" id="lamp-card-fan-trip">송풍기 트립 <span class="hmi-lamp" id="lamp-fan-trip"></span></div>
|
||||
<div class="btn-hmi" style="flex:1; cursor:default; background:var(--overlay-subtle);" id="lamp-card-heater-req">히터 제어 <span class="hmi-lamp" id="lamp-heater-req"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4그룹: 커팅부 작동 -->
|
||||
<div class="hmi-group-card">
|
||||
<div class="hmi-group-title">
|
||||
<span>✂️ 커팅부</span>
|
||||
<div style="display: flex; gap: 6px;">
|
||||
<button class="btn-hmi" id="btn-cutter-group-on" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(8, 'cutter_on')">ON <span class="hmi-lamp" id="lamp-cutter-on"></span></button>
|
||||
<button class="btn-hmi" id="btn-cutter-group-off" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(9, 'cutter_off')">OFF <span class="hmi-lamp" id="lamp-cutter-off"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 기존 조작 버튼들 -->
|
||||
<button class="btn-hmi" id="btn-cutter-motor" onclick="sendHmiBit(48, 'cutter_motor')">커팅모터 작동 기동 <span class="hmi-lamp" id="lamp-cutter-motor-start"></span></button>
|
||||
<div style="display:flex; justify-content:space-between; gap:10px;">
|
||||
<div class="btn-hmi" style="flex:1; cursor:default; background:var(--overlay-subtle);">모터 활성 피드백 <span class="hmi-lamp" id="lamp-cutter-motor-active"></span></div>
|
||||
</div>
|
||||
<div class="register-row">
|
||||
<span class="lamp-label">실시간 커팅모터 속도</span>
|
||||
<span class="register-value" id="val-cutter-speed">0.0 <span style="font-size:11px; font-weight:normal; color:var(--text-secondary);">Hz</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5그룹: 이송 작동 -->
|
||||
<div class="hmi-group-card">
|
||||
<div class="hmi-group-title">
|
||||
<span>🔄 이송부</span>
|
||||
<div style="display: flex; gap: 6px;">
|
||||
<button class="btn-hmi" id="btn-transfer-group-on" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(10, 'transfer_on')">ON <span class="hmi-lamp" id="lamp-transfer-on"></span></button>
|
||||
<button class="btn-hmi" id="btn-transfer-group-off" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(11, 'transfer_off')">OFF <span class="hmi-lamp" id="lamp-transfer-off"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 기존 조작 버튼들 -->
|
||||
<div style="display:flex; justify-content:space-between; gap:10px;">
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-transfer-auto" onclick="sendHmiBit(50, 'transfer_auto')">자동 모드 <span class="hmi-lamp" id="lamp-transfer-auto"></span></button>
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-transfer-manual" onclick="sendHmiBit(51, 'transfer_manual')">수동 모드 <span class="hmi-lamp" id="lamp-transfer-manual"></span></button>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; gap:10px;">
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-transfer-auto-start" onclick="sendHmiBit(52, 'transfer_auto_start')">자동 시작 <span class="hmi-lamp" id="lamp-transfer-auto-start"></span></button>
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-transfer-auto-stop" onclick="sendHmiBit(53, 'transfer_auto_stop')">자동 정지 <span class="hmi-lamp" id="lamp-transfer-auto-stop"></span></button>
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns: repeat(3, 1fr); gap:8px;">
|
||||
<button class="btn-hmi" style="padding:10px 4px; font-size:12px;" id="btn-transfer-fwd" onclick="sendHmiBit(54, 'transfer_fwd')">수동(정) <span class="hmi-lamp" id="lamp-transfer-fwd"></span></button>
|
||||
<button class="btn-hmi" style="padding:10px 4px; font-size:12px;" id="btn-transfer-rev" onclick="sendHmiBit(55, 'transfer_rev')">수동(역) <span class="hmi-lamp" id="lamp-transfer-rev"></span></button>
|
||||
<button class="btn-hmi btn-danger" style="padding:10px 4px; font-size:12px; background: rgba(239,68,68,0.15);" id="btn-transfer-stop" onclick="sendHmiBit(56, 'transfer_stop')">수동 정지 <span class="hmi-lamp" id="lamp-transfer-stop"></span></button>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; gap:10px;">
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-transfer-zero" onclick="sendHmiBit(57, 'transfer_zero')">제로셋 <span class="hmi-lamp" id="lamp-transfer-zero"></span></button>
|
||||
<button class="btn-hmi" style="flex:1;" id="btn-transfer-err-reset" onclick="sendHmiBit(58, 'transfer_err_reset')">에러리셋 <span class="hmi-lamp" id="lamp-transfer-err-reset"></span></button>
|
||||
</div>
|
||||
|
||||
<div class="register-row">
|
||||
<span class="lamp-label">현재 이동량 (1초 기준)</span>
|
||||
<span class="register-value" id="val-transfer-cur-pos">0.0 <span style="font-size:11px; font-weight:normal; color:var(--text-secondary);">mm/s</span></span>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 10px;">
|
||||
<!-- 자동 목표 이동량 설정 -->
|
||||
<div style="display: flex; flex-direction: column; gap: 6px; background: var(--overlay-subtle); border: 1px solid var(--border-color); padding: 8px 12px; border-radius: 8px;">
|
||||
<span class="lamp-label" style="font-size: 13px; color: var(--text-primary); font-weight: 600;">자동 이동량 (mm/rev)</span>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; align-items: center; width: 100%;">
|
||||
<span style="font-size: 13px; color: var(--text-primary); white-space: nowrap; text-align: left;">현: <span id="val-transfer-target-pos" style="color: var(--text-primary); font-weight: 600;">0</span></span>
|
||||
<input type="number" class="input-inline" style="width: 100%; box-sizing: border-box; padding: 4px 6px; font-size: 12px; text-align: center;" id="input-transfer-target-pos" value="0">
|
||||
</div>
|
||||
<button class="btn btn-primary" style="width: 100%; padding: 6px; font-size: 11px; white-space: nowrap; margin-top: 2px;" onclick="sendHmiRegister(0, 'input-transfer-target-pos')">전송</button>
|
||||
</div>
|
||||
<!-- 수동 목표 속도 설정 -->
|
||||
<div style="display: flex; flex-direction: column; gap: 6px; background: var(--overlay-subtle); border: 1px solid var(--border-color); padding: 8px 12px; border-radius: 8px;">
|
||||
<span class="lamp-label" style="font-size: 13px; color: var(--text-primary); font-weight: 600;">수동 속도 (mm/s)</span>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; align-items: center; width: 100%;">
|
||||
<span style="font-size: 13px; color: var(--text-primary); white-space: nowrap; text-align: left;">현: <span id="val-transfer-target-speed" style="color: var(--text-primary); font-weight: 600;">0</span></span>
|
||||
<input type="number" class="input-inline" style="width: 100%; box-sizing: border-box; padding: 4px 6px; font-size: 12px; text-align: center;" id="input-transfer-target-speed" value="0">
|
||||
</div>
|
||||
<button class="btn btn-primary" style="width: 100%; padding: 6px; font-size: 11px; white-space: nowrap; margin-top: 2px;" onclick="sendHmiRegister(1, 'input-transfer-target-speed')">전송</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6그룹: 행거부 작동 -->
|
||||
<div class="hmi-group-card" style="position:relative;">
|
||||
<div class="hmi-group-title">
|
||||
<span>🪝 행거부</span>
|
||||
<div style="display: flex; gap: 6px;">
|
||||
<button class="btn-hmi" id="btn-impreg-hanger-on-g6" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(4, 'impreg_hanger_on')">ON <span class="hmi-lamp" id="lamp-impreg-hanger-on-g6"></span></button>
|
||||
<button class="btn-hmi" id="btn-impreg-hanger-off-g6" style="padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; gap: 6px;" onclick="sendHmiBit(5, 'impreg_hanger_off')">OFF <span class="hmi-lamp" id="lamp-impreg-hanger-off-g6"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 기존 조작 영역 -->
|
||||
<div class="port-grid" id="hanger-ports-container">
|
||||
<!-- 20개 컴팩트 셀 JS로 동적 렌더링 -->
|
||||
</div>
|
||||
|
||||
<!-- 공용 팝오버 오버레이 -->
|
||||
<div class="hmi-popover" id="hanger-popover">
|
||||
<h4 style="margin:0 0 6px 0; font-size:11px; color:var(--text-primary); text-align:center;" id="popover-title">P01 제어</h4>
|
||||
<button class="btn btn-primary" id="btn-popover-auto-down">자동하강</button>
|
||||
<button class="btn" style="background:rgba(245,158,11,0.2); border-color:var(--accent-orange); color:var(--accent-orange);" id="btn-popover-manual-down">수동하강</button>
|
||||
<button class="btn" style="background:rgba(16,185,129,0.2); border-color:var(--accent-green); color:var(--accent-green);" id="btn-popover-manual-up">수동상승</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 신규 설비 등록 모달 -->
|
||||
<div class="modal" id="add-equipment-modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">신규 설비 등록</div>
|
||||
<form id="add-equipment-form" onsubmit="saveEquipment(event)">
|
||||
<div class="form-group">
|
||||
<label for="eq-id">설비 코드 (Unique ID)</label>
|
||||
<input type="text" id="eq-id" class="form-control" placeholder="예: CC_MC_TYPE_A" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="eq-name">설비명</label>
|
||||
<input type="text" id="eq-name" class="form-control" placeholder="예: 카본절단기 1호기" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="eq-type">설비 타입</label>
|
||||
<input type="text" id="eq-type" class="form-control" placeholder="예: cutter" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="eq-ip">IP 주소</label>
|
||||
<input type="text" id="eq-ip" class="form-control" placeholder="예: 192.168.1.100" required>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn" onclick="closeAddModal()" style="background:transparent; color:var(--text-secondary);">취소</button>
|
||||
<button type="submit" class="btn btn-primary">등록하기</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 스크립트 파일 연결 -->
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/equipment.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user