운전시 버그 수정
This commit is contained in:
@@ -498,6 +498,91 @@ tr:last-child td { border-bottom: none; }
|
||||
.pb-name-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
|
||||
/* ── Custom DateTime Picker ──────────────────────────────── */
|
||||
.dt-display {
|
||||
cursor: pointer; user-select: none;
|
||||
color: var(--t0); display: flex; align-items: center;
|
||||
}
|
||||
.dt-display:hover { border-color: var(--a); }
|
||||
|
||||
.dt-overlay {
|
||||
position: fixed; inset: 0; z-index: 900;
|
||||
}
|
||||
|
||||
.dt-popup {
|
||||
position: fixed; z-index: 901;
|
||||
background: var(--s2); border: 1px solid var(--bd2);
|
||||
border-radius: var(--rl); padding: 16px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,.6);
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.dt-cal-nav {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.dt-month-label { font-weight: 700; color: var(--t0); font-size: 14px; }
|
||||
.dt-nav-btn {
|
||||
background: var(--s3); border: 1px solid var(--bd);
|
||||
color: var(--t1); border-radius: var(--r);
|
||||
width: 28px; height: 28px; cursor: pointer; font-size: 16px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: all var(--tr);
|
||||
}
|
||||
.dt-nav-btn:hover { background: var(--s4); color: var(--t0); }
|
||||
|
||||
.dt-cal-grid {
|
||||
display: grid; grid-template-columns: repeat(7, 1fr);
|
||||
gap: 2px; margin-bottom: 12px;
|
||||
}
|
||||
.dt-dow {
|
||||
text-align: center; font-size: 11px; font-weight: 600;
|
||||
color: var(--t2); padding: 4px 0;
|
||||
}
|
||||
.dt-day {
|
||||
text-align: center; padding: 6px 2px; font-size: 12px;
|
||||
border-radius: var(--r); cursor: pointer; color: var(--t1);
|
||||
transition: background var(--tr);
|
||||
}
|
||||
.dt-day:hover { background: var(--s4); color: var(--t0); }
|
||||
.dt-day.other-month { color: var(--t2); }
|
||||
.dt-day.today { color: var(--a); font-weight: 700; }
|
||||
.dt-day.selected {
|
||||
background: var(--a); color: #000; font-weight: 700;
|
||||
}
|
||||
.dt-day.selected:hover { background: var(--a2); }
|
||||
|
||||
.dt-time-row {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 10px 0; border-top: 1px solid var(--bd);
|
||||
border-bottom: 1px solid var(--bd); margin-bottom: 12px;
|
||||
}
|
||||
.dt-time-label { color: var(--t2); font-size: 12px; flex: 1; }
|
||||
.dt-time-sep { color: var(--t1); font-weight: 700; font-size: 16px; }
|
||||
.dt-time-ctrl {
|
||||
display: flex; align-items: center; gap: 4px;
|
||||
}
|
||||
.dt-time-ctrl button {
|
||||
background: var(--s3); border: 1px solid var(--bd);
|
||||
color: var(--t1); border-radius: var(--r);
|
||||
width: 24px; height: 24px; cursor: pointer; font-size: 14px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: all var(--tr);
|
||||
}
|
||||
.dt-time-ctrl button:hover { background: var(--s4); color: var(--t0); }
|
||||
.dt-time-inp {
|
||||
width: 42px; text-align: center;
|
||||
background: var(--s3); border: 1px solid var(--bd);
|
||||
color: var(--t0); border-radius: var(--r);
|
||||
padding: 3px 4px; font-size: 14px; font-family: var(--fm);
|
||||
}
|
||||
.dt-time-inp::-webkit-inner-spin-button,
|
||||
.dt-time-inp::-webkit-outer-spin-button { -webkit-appearance: none; }
|
||||
|
||||
.dt-pop-btns {
|
||||
display: flex; justify-content: flex-end; gap: 6px;
|
||||
}
|
||||
|
||||
/* ── Utility ─────────────────────────────────────────────── */
|
||||
.hidden { display: none !important; }
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<div class="card-cap">단일 태그 읽기</div>
|
||||
<div class="row-inp">
|
||||
<input id="x-node" class="inp flex1"
|
||||
value="ns=1;s=shinam:p-6102.hzset.fieldvalue"
|
||||
value="ns=1;s=sinamserver:p-6102.hzset.fieldvalue"
|
||||
placeholder="ns=1;s=..."/>
|
||||
<button class="btn-b" onclick="connRead()">읽기</button>
|
||||
</div>
|
||||
@@ -194,7 +194,7 @@
|
||||
<div class="card">
|
||||
<div class="card-cap">수집 노드 목록 <em>(한 줄에 하나씩)</em></div>
|
||||
<textarea id="w-nodes" class="ta" rows="9"
|
||||
placeholder="ns=1;s=...">ns=1;s=shinam:p-6102.hzset.fieldvalue</textarea>
|
||||
placeholder="ns=1;s=...">ns=1;s=sinamserver:p-6102.hzset.fieldvalue</textarea>
|
||||
<button class="btn-a" id="crawl-btn" onclick="crawlStart()"
|
||||
style="margin-top:14px">📡 크롤링 시작</button>
|
||||
</div>
|
||||
@@ -430,7 +430,7 @@
|
||||
<div class="card-cap">수동 포인트 추가</div>
|
||||
<div class="fg">
|
||||
<label>Node ID 직접 입력</label>
|
||||
<input id="pb-manual-nid" class="inp" placeholder="ns=2;s=Honeywell.Experion..."/>
|
||||
<input id="pb-manual-nid" class="inp" placeholder="ns=1;s=tagname.pv..."/>
|
||||
</div>
|
||||
<button class="btn-b" onclick="pbAddManual()">+ 추가</button>
|
||||
<div id="pb-manual-log" class="logbox hidden" style="margin-top:10px"></div>
|
||||
@@ -512,11 +512,13 @@
|
||||
<div class="cols-3">
|
||||
<div class="fg">
|
||||
<label>시작 시간</label>
|
||||
<input id="hf-from" class="inp" type="datetime-local"/>
|
||||
<input type="hidden" id="hf-from"/>
|
||||
<div class="dt-display inp" id="dtp-from-display" onclick="dtOpen('from')">— 선택 안 함 —</div>
|
||||
</div>
|
||||
<div class="fg">
|
||||
<label>종료 시간</label>
|
||||
<input id="hf-to" class="inp" type="datetime-local"/>
|
||||
<input type="hidden" id="hf-to"/>
|
||||
<div class="dt-display inp" id="dtp-to-display" onclick="dtOpen('to')">— 선택 안 함 —</div>
|
||||
</div>
|
||||
<div class="fg">
|
||||
<label>최대 행 수</label>
|
||||
@@ -536,6 +538,36 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- ── 날짜/시간 선택 팝업 ──────────────────────────────────── -->
|
||||
<div id="dt-overlay" class="dt-overlay hidden" onclick="dtCancel()"></div>
|
||||
<div id="dt-popup" class="dt-popup hidden">
|
||||
<div class="dt-cal-nav">
|
||||
<button class="dt-nav-btn" onclick="dtPrevMonth()">‹</button>
|
||||
<span id="dt-month-label" class="dt-month-label"></span>
|
||||
<button class="dt-nav-btn" onclick="dtNextMonth()">›</button>
|
||||
</div>
|
||||
<div class="dt-cal-grid" id="dt-cal-grid"></div>
|
||||
<div class="dt-time-row">
|
||||
<span class="dt-time-label">시간</span>
|
||||
<div class="dt-time-ctrl">
|
||||
<button onclick="dtAdjTime('h',-1)">−</button>
|
||||
<input id="dt-hour" class="dt-time-inp" type="number" min="0" max="23" value="0" oninput="dtClampTime('h',this)"/>
|
||||
<button onclick="dtAdjTime('h', 1)">+</button>
|
||||
</div>
|
||||
<span class="dt-time-sep">:</span>
|
||||
<div class="dt-time-ctrl">
|
||||
<button onclick="dtAdjTime('m',-1)">−</button>
|
||||
<input id="dt-min" class="dt-time-inp" type="number" min="0" max="59" value="0" oninput="dtClampTime('m',this)"/>
|
||||
<button onclick="dtAdjTime('m', 1)">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dt-pop-btns">
|
||||
<button class="btn-b btn-sm" onclick="dtClear()">지우기</button>
|
||||
<button class="btn-b btn-sm" onclick="dtCancel()">취소</button>
|
||||
<button class="btn-a btn-sm" onclick="dtConfirm()">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -748,12 +748,134 @@ async function histQuery() {
|
||||
|
||||
function histReset() {
|
||||
HIST_TAG_IDS.forEach(id => { document.getElementById(id).value = ''; });
|
||||
document.getElementById('hf-from').value = '';
|
||||
document.getElementById('hf-to').value = '';
|
||||
dtClearField('from');
|
||||
dtClearField('to');
|
||||
document.getElementById('hf-limit').value = '500';
|
||||
document.getElementById('hist-result-info').classList.add('hidden');
|
||||
document.getElementById('hist-table').classList.add('hidden');
|
||||
}
|
||||
|
||||
/* ── Custom DateTime Picker ──────────────────────────────────── */
|
||||
const _dtp = { target: null, year: 0, month: 0,
|
||||
selYear: null, selMonth: null, selDay: null,
|
||||
selHour: 0, selMin: 0 };
|
||||
|
||||
const _DTP_DAYS = ['일','월','화','수','목','금','토'];
|
||||
|
||||
function dtOpen(target) {
|
||||
_dtp.target = target;
|
||||
const hidden = document.getElementById(`hf-${target}`).value;
|
||||
const d = hidden ? new Date(hidden) : new Date();
|
||||
_dtp.year = d.getFullYear();
|
||||
_dtp.month = d.getMonth();
|
||||
if (hidden && !isNaN(d)) {
|
||||
_dtp.selYear = d.getFullYear(); _dtp.selMonth = d.getMonth();
|
||||
_dtp.selDay = d.getDate();
|
||||
_dtp.selHour = d.getHours(); _dtp.selMin = d.getMinutes();
|
||||
} else {
|
||||
_dtp.selYear = null; _dtp.selMonth = null; _dtp.selDay = null;
|
||||
_dtp.selHour = 0; _dtp.selMin = 0;
|
||||
}
|
||||
document.getElementById('dt-hour').value = String(_dtp.selHour).padStart(2,'0');
|
||||
document.getElementById('dt-min').value = String(_dtp.selMin).padStart(2,'0');
|
||||
dtRenderCal();
|
||||
|
||||
// 팝업 위치 계산
|
||||
const popup = document.getElementById('dt-popup');
|
||||
const display = document.getElementById(`dtp-${target}-display`);
|
||||
const rect = display.getBoundingClientRect();
|
||||
popup.classList.remove('hidden');
|
||||
document.getElementById('dt-overlay').classList.remove('hidden');
|
||||
|
||||
// 화면 아래 공간 부족하면 위쪽으로
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
if (spaceBelow < popup.offsetHeight + 8) {
|
||||
popup.style.top = (rect.top - popup.offsetHeight - 4) + 'px';
|
||||
} else {
|
||||
popup.style.top = (rect.bottom + 4) + 'px';
|
||||
}
|
||||
popup.style.left = Math.min(rect.left, window.innerWidth - 296) + 'px';
|
||||
}
|
||||
|
||||
function dtRenderCal() {
|
||||
document.getElementById('dt-month-label').textContent =
|
||||
`${_dtp.year}년 ${_dtp.month + 1}월`;
|
||||
const first = new Date(_dtp.year, _dtp.month, 1).getDay();
|
||||
const daysInMon = new Date(_dtp.year, _dtp.month + 1, 0).getDate();
|
||||
const daysInPrev = new Date(_dtp.year, _dtp.month, 0).getDate();
|
||||
const today = new Date();
|
||||
let html = _DTP_DAYS.map(d => `<div class="dt-dow">${d}</div>`).join('');
|
||||
|
||||
for (let i = first - 1; i >= 0; i--)
|
||||
html += `<div class="dt-day other-month">${daysInPrev - i}</div>`;
|
||||
|
||||
for (let d = 1; d <= daysInMon; d++) {
|
||||
let cls = 'dt-day';
|
||||
if (_dtp.year === today.getFullYear() && _dtp.month === today.getMonth() && d === today.getDate())
|
||||
cls += ' today';
|
||||
if (_dtp.selYear === _dtp.year && _dtp.selMonth === _dtp.month && _dtp.selDay === d)
|
||||
cls += ' selected';
|
||||
html += `<div class="${cls}" onclick="dtSelectDay(${d})">${d}</div>`;
|
||||
}
|
||||
const trailing = (first + daysInMon) % 7;
|
||||
for (let d = 1; d <= (trailing ? 7 - trailing : 0); d++)
|
||||
html += `<div class="dt-day other-month">${d}</div>`;
|
||||
|
||||
document.getElementById('dt-cal-grid').innerHTML = html;
|
||||
}
|
||||
|
||||
function dtSelectDay(day) {
|
||||
_dtp.selYear = _dtp.year; _dtp.selMonth = _dtp.month; _dtp.selDay = day;
|
||||
dtRenderCal();
|
||||
}
|
||||
function dtPrevMonth() {
|
||||
if (--_dtp.month < 0) { _dtp.month = 11; _dtp.year--; }
|
||||
dtRenderCal();
|
||||
}
|
||||
function dtNextMonth() {
|
||||
if (++_dtp.month > 11) { _dtp.month = 0; _dtp.year++; }
|
||||
dtRenderCal();
|
||||
}
|
||||
function dtAdjTime(part, delta) {
|
||||
if (part === 'h') {
|
||||
_dtp.selHour = ((_dtp.selHour + delta) + 24) % 24;
|
||||
document.getElementById('dt-hour').value = String(_dtp.selHour).padStart(2,'0');
|
||||
} else {
|
||||
_dtp.selMin = ((_dtp.selMin + delta) + 60) % 60;
|
||||
document.getElementById('dt-min').value = String(_dtp.selMin).padStart(2,'0');
|
||||
}
|
||||
}
|
||||
function dtClampTime(part, el) {
|
||||
const max = part === 'h' ? 23 : 59;
|
||||
let v = parseInt(el.value);
|
||||
if (isNaN(v) || v < 0) v = 0;
|
||||
if (v > max) v = max;
|
||||
el.value = String(v).padStart(2,'0');
|
||||
if (part === 'h') _dtp.selHour = v; else _dtp.selMin = v;
|
||||
}
|
||||
function dtConfirm() {
|
||||
if (_dtp.selDay === null) { alert('날짜를 선택하세요.'); return; }
|
||||
_dtp.selHour = parseInt(document.getElementById('dt-hour').value) || 0;
|
||||
_dtp.selMin = parseInt(document.getElementById('dt-min').value) || 0;
|
||||
const p = n => String(n).padStart(2,'0');
|
||||
const val = `${_dtp.selYear}-${p(_dtp.selMonth+1)}-${p(_dtp.selDay)}T${p(_dtp.selHour)}:${p(_dtp.selMin)}`;
|
||||
document.getElementById(`hf-${_dtp.target}`).value = val;
|
||||
document.getElementById(`dtp-${_dtp.target}-display`).textContent =
|
||||
`${_dtp.selYear}-${p(_dtp.selMonth+1)}-${p(_dtp.selDay)} ${p(_dtp.selHour)}:${p(_dtp.selMin)}`;
|
||||
dtClose();
|
||||
}
|
||||
function dtClear() { dtClearField(_dtp.target); dtClose(); }
|
||||
function dtClearField(target) {
|
||||
if (!target) return;
|
||||
document.getElementById(`hf-${target}`).value = '';
|
||||
document.getElementById(`dtp-${target}-display`).textContent = '— 선택 안 함 —';
|
||||
}
|
||||
function dtCancel() { dtClose(); }
|
||||
function dtClose() {
|
||||
document.getElementById('dt-popup').classList.add('hidden');
|
||||
document.getElementById('dt-overlay').classList.add('hidden');
|
||||
_dtp.target = null;
|
||||
}
|
||||
|
||||
/* ── 초기 실행 ───────────────────────────────────────────────── */
|
||||
certStatus();
|
||||
|
||||
Reference in New Issue
Block a user