버젼 1.5 로깅데이터 모니터링, 파일삭제기능 추가

This commit is contained in:
2025-11-05 18:30:47 +00:00
parent 41e8d18072
commit 2ee1ad905e
2 changed files with 568 additions and 347 deletions

254
index.h
View File

@@ -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();
});