버젼 1.5 로깅데이터 모니터링, 파일삭제기능 추가
This commit is contained in:
254
index.h
254
index.h
@@ -349,6 +349,10 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.file-item:hover { transform: translateX(5px); box-shadow: 0 3px 10px rgba(0,0,0,0.1); }
|
||||
.file-item.logging {
|
||||
border: 2px solid #11998e;
|
||||
background: linear-gradient(to right, rgba(17, 153, 142, 0.05), white);
|
||||
}
|
||||
.file-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@@ -361,18 +365,46 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
color: #333;
|
||||
font-size: 0.9em;
|
||||
word-break: break-all;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.logging-badge {
|
||||
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
.file-size {
|
||||
color: #666;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.download-btn {
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.download-btn, .delete-btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 0.85em;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
min-width: 100px;
|
||||
min-width: 80px;
|
||||
}
|
||||
.delete-btn {
|
||||
background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
|
||||
}
|
||||
.delete-btn:hover {
|
||||
background: linear-gradient(135deg, #d32f3f 0%, #e53935 100%);
|
||||
}
|
||||
.delete-btn:disabled {
|
||||
background: #cccccc;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -399,29 +431,16 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
.time-value {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* 파일 목록 모바일 최적화 */
|
||||
.file-item {
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.file-info {
|
||||
.btn-time-sync {
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.file-name {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.file-size {
|
||||
font-size: 0.8em;
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.download-btn {
|
||||
.file-actions {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
min-width: auto;
|
||||
justify-content: stretch;
|
||||
}
|
||||
.download-btn, .delete-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -429,70 +448,67 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Byun CAN Logger</h1>
|
||||
<p>Real-time CAN Bus Monitor & Data Logger with Time Sync</p>
|
||||
<h1>🚗 Byun CAN Logger v1.5</h1>
|
||||
<p>Real-time CAN Bus Monitor & Logger + File Management</p>
|
||||
</div>
|
||||
|
||||
<div class="nav">
|
||||
<a href="/" class="active">Monitor</a>
|
||||
<a href="/transmit">Transmit</a>
|
||||
<a href="/graph">Graph</a>
|
||||
<a href="/" class="active">📊 Monitor</a>
|
||||
<a href="/transmit">📤 Transmit</a>
|
||||
<a href="/graph">📈 Graph</a>
|
||||
<a href="/graph-view">📊 Graph View</a>
|
||||
<a href="/settings">⚙️ Settings</a>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 큐 상태 표시 -->
|
||||
<div class="queue-status" id="queue-status">
|
||||
<div class="queue-info">
|
||||
<span style="font-size: 1.3em;">📊</span>
|
||||
<span style="font-weight: 600; font-size: 0.85em;">CAN Queue</span>
|
||||
</div>
|
||||
<div style="flex: 1; min-width: 200px;">
|
||||
<div class="queue-bar-container">
|
||||
<div class="queue-bar" id="queue-bar" style="width: 0%"></div>
|
||||
<div class="queue-text" id="queue-text">0/1000</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 전력 상태 표시 -->
|
||||
<div class="power-status" id="power-status">
|
||||
<div class="power-status-label">
|
||||
<span id="power-icon">⚡</span>
|
||||
<span id="power-text">전원 상태</span>
|
||||
</div>
|
||||
<div class="power-status-values">
|
||||
<div class="power-status-item">
|
||||
<span class="power-status-item-label">현재</span>
|
||||
<span class="power-status-value" id="power-value">--V</span>
|
||||
</div>
|
||||
<div class="power-status-item">
|
||||
<span class="power-status-item-label">최소(1s)</span>
|
||||
<span class="power-status-value" id="power-min-value">--V</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="time-sync-banner">
|
||||
<div class="time-sync-info">
|
||||
<div class="time-info-item">
|
||||
<span class="time-label">⏰ 시간 동기화 상태</span>
|
||||
<span class="time-value" id="sync-status">대기 중...</span>
|
||||
<div class="time-label">CURRENT TIME</div>
|
||||
<div class="time-value" id="current-time">--:--:--</div>
|
||||
</div>
|
||||
<div class="time-info-item">
|
||||
<span class="time-label">🕐 현재 시간</span>
|
||||
<span class="time-value" id="current-time">--:--:--</span>
|
||||
<div class="time-label">CONNECTION</div>
|
||||
<div class="time-value" id="sync-status">연결 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-time-sync" onclick="syncTime()">⏰ 시간 동기화</button>
|
||||
<button class="btn-time-sync" onclick="syncTime()">🕐 Sync Time</button>
|
||||
</div>
|
||||
|
||||
<div class="power-status" id="power-status">
|
||||
<div class="power-status-label">
|
||||
<span>⚡</span>
|
||||
<span>POWER STATUS</span>
|
||||
</div>
|
||||
<div class="power-status-values">
|
||||
<div class="power-status-item">
|
||||
<div class="power-status-item-label">CURRENT</div>
|
||||
<div class="power-status-value" id="voltage-current">-.--V</div>
|
||||
</div>
|
||||
<div class="power-status-item">
|
||||
<div class="power-status-item-label">MIN (1s)</div>
|
||||
<div class="power-status-value" id="voltage-min">-.--V</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="queue-status" id="queue-status">
|
||||
<div class="queue-info">
|
||||
<span style="font-size: 1.2em;">📦</span>
|
||||
<span style="font-weight: 700; font-size: 0.9em;">QUEUE STATUS</span>
|
||||
</div>
|
||||
<div class="queue-bar-container">
|
||||
<div class="queue-bar" id="queue-bar" style="width: 0%;"></div>
|
||||
<div class="queue-text" id="queue-text">0 / 1000</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-grid">
|
||||
<div class="status-card" id="logging-status">
|
||||
<div class="status-card status-off" id="logging-status">
|
||||
<h3>LOGGING</h3>
|
||||
<div class="value">OFF</div>
|
||||
</div>
|
||||
<div class="status-card" id="sd-status">
|
||||
<div class="status-card status-off" id="sd-status">
|
||||
<h3>SD CARD</h3>
|
||||
<div class="value">NOT READY</div>
|
||||
</div>
|
||||
@@ -512,6 +528,10 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<h3>CURRENT FILE</h3>
|
||||
<div class="value" id="current-file" style="font-size: 0.85em;">-</div>
|
||||
</div>
|
||||
<div class="status-card" id="filesize-status">
|
||||
<h3>FILE SIZE</h3>
|
||||
<div class="value" id="current-file-size">0 B</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-panel">
|
||||
@@ -564,6 +584,7 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
let messageOrder = [];
|
||||
let lastMessageData = {};
|
||||
const speedNames = {0: '125K', 1: '250K', 2: '500K', 3: '1M'};
|
||||
let currentLoggingFile = '';
|
||||
|
||||
function updateCurrentTime() {
|
||||
const now = new Date();
|
||||
@@ -636,6 +657,8 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
updateCanBatch(data.messages);
|
||||
} else if (data.type === 'files') {
|
||||
updateFileList(data.files);
|
||||
} else if (data.type === 'deleteResult') {
|
||||
handleDeleteResult(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Parse error:', e);
|
||||
@@ -648,6 +671,7 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
const sdCard = document.getElementById('sd-status');
|
||||
const timeSyncCard = document.getElementById('time-sync-card');
|
||||
const fileCard = document.getElementById('file-status');
|
||||
const filesizeCard = document.getElementById('filesize-status');
|
||||
|
||||
if (data.logging) {
|
||||
loggingCard.classList.add('status-on');
|
||||
@@ -684,10 +708,26 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
if (data.currentFile && data.currentFile !== '') {
|
||||
fileCard.classList.add('status-on');
|
||||
fileCard.classList.remove('status-off');
|
||||
document.getElementById('current-file').textContent = data.currentFile;
|
||||
const filename = data.currentFile.startsWith('/') ? data.currentFile.substring(1) : data.currentFile;
|
||||
document.getElementById('current-file').textContent = filename;
|
||||
currentLoggingFile = filename;
|
||||
} else {
|
||||
fileCard.classList.remove('status-on', 'status-off');
|
||||
document.getElementById('current-file').textContent = '-';
|
||||
currentLoggingFile = '';
|
||||
}
|
||||
|
||||
// 실시간 파일 크기 표시
|
||||
if (data.currentFileSize !== undefined) {
|
||||
const sizeStr = formatBytes(data.currentFileSize);
|
||||
document.getElementById('current-file-size').textContent = sizeStr;
|
||||
|
||||
if (data.currentFileSize > 0) {
|
||||
filesizeCard.classList.add('status-on');
|
||||
filesizeCard.classList.remove('status-off');
|
||||
} else {
|
||||
filesizeCard.classList.remove('status-on', 'status-off');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('msg-count').textContent = data.msgCount.toLocaleString();
|
||||
@@ -699,42 +739,29 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
const queueBar = document.getElementById('queue-bar');
|
||||
const queueText = document.getElementById('queue-text');
|
||||
|
||||
const queuePercent = (data.queueUsed / data.queueSize) * 100;
|
||||
queueBar.style.width = queuePercent + '%';
|
||||
queueText.textContent = data.queueUsed + '/' + data.queueSize;
|
||||
const percentage = (data.queueUsed / data.queueSize) * 100;
|
||||
queueBar.style.width = percentage + '%';
|
||||
queueText.textContent = data.queueUsed + ' / ' + data.queueSize;
|
||||
|
||||
// 큐 상태에 따른 색상 변경
|
||||
queueStatus.classList.remove('warning', 'critical');
|
||||
if (queuePercent >= 90) {
|
||||
if (percentage >= 90) {
|
||||
queueStatus.classList.add('critical');
|
||||
} else if (queuePercent >= 70) {
|
||||
} else if (percentage >= 70) {
|
||||
queueStatus.classList.add('warning');
|
||||
}
|
||||
}
|
||||
|
||||
// 전력 상태 업데이트
|
||||
if (data.voltage !== undefined) {
|
||||
const powerStatus = document.getElementById('power-status');
|
||||
const powerValue = document.getElementById('power-value');
|
||||
const powerMinValue = document.getElementById('power-min-value');
|
||||
const powerIcon = document.getElementById('power-icon');
|
||||
const powerText = document.getElementById('power-text');
|
||||
|
||||
powerValue.textContent = data.voltage.toFixed(2) + 'V';
|
||||
|
||||
if (data.minVoltage !== undefined) {
|
||||
powerMinValue.textContent = data.minVoltage.toFixed(2) + 'V';
|
||||
}
|
||||
|
||||
if (data.lowVoltage) {
|
||||
powerStatus.classList.add('low');
|
||||
powerIcon.textContent = '⚠️';
|
||||
powerText.textContent = '전력 부족 경고!';
|
||||
} else {
|
||||
powerStatus.classList.remove('low');
|
||||
powerIcon.textContent = '⚡';
|
||||
powerText.textContent = '전원 정상';
|
||||
}
|
||||
document.getElementById('voltage-current').textContent = data.voltage.toFixed(2) + 'V';
|
||||
}
|
||||
if (data.minVoltage !== undefined) {
|
||||
document.getElementById('voltage-min').textContent = data.minVoltage.toFixed(2) + 'V';
|
||||
}
|
||||
if (data.lowVoltage !== undefined && data.lowVoltage) {
|
||||
document.getElementById('power-status').classList.add('low');
|
||||
} else {
|
||||
document.getElementById('power-status').classList.remove('low');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,14 +866,26 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
|
||||
fileList.innerHTML = '';
|
||||
files.forEach(file => {
|
||||
const isLogging = (currentLoggingFile && file.name === currentLoggingFile);
|
||||
const fileItem = document.createElement('div');
|
||||
fileItem.className = 'file-item';
|
||||
fileItem.className = 'file-item' + (isLogging ? ' logging' : '');
|
||||
|
||||
let nameHtml = '<div class="file-name">' + file.name;
|
||||
if (isLogging) {
|
||||
nameHtml += '<span class="logging-badge">LOGGING</span>';
|
||||
}
|
||||
nameHtml += '</div>';
|
||||
|
||||
fileItem.innerHTML =
|
||||
'<div class="file-info">' +
|
||||
'<div class="file-name">' + file.name + '</div>' +
|
||||
nameHtml +
|
||||
'<div class="file-size">' + formatBytes(file.size) + '</div>' +
|
||||
'</div>' +
|
||||
'<button class="download-btn" onclick="downloadFile(\'' + file.name + '\')">Download</button>';
|
||||
'<div class="file-actions">' +
|
||||
'<button class="download-btn" onclick="downloadFile(\'' + file.name + '\')">Download</button>' +
|
||||
'<button class="delete-btn" onclick="deleteFile(\'' + file.name + '\')" ' +
|
||||
(isLogging ? 'disabled title="Cannot delete file being logged"' : '') + '>Delete</button>' +
|
||||
'</div>';
|
||||
fileList.appendChild(fileItem);
|
||||
});
|
||||
}
|
||||
@@ -909,6 +948,27 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
window.location.href = '/download?file=' + encodeURIComponent(filename);
|
||||
}
|
||||
|
||||
function deleteFile(filename) {
|
||||
if (!confirm('Are you sure you want to delete "' + filename + '"?\n\nThis action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({cmd: 'deleteFile', filename: filename}));
|
||||
console.log('Delete file command sent:', filename);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeleteResult(data) {
|
||||
if (data.success) {
|
||||
console.log('File deleted successfully');
|
||||
// 파일 목록은 서버에서 자동으로 갱신됨
|
||||
} else {
|
||||
alert('Failed to delete file: ' + data.message);
|
||||
console.error('Delete failed:', data.message);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
loadCanSpeed();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user