feat: replace all emojis with SVG icons

This commit is contained in:
2025-10-14 10:17:38 +02:00
parent 55bc38577c
commit fa6d72ea62
10 changed files with 240 additions and 73 deletions

View File

@@ -302,7 +302,7 @@ class ClusterMembersComponent extends Component {
const statusElement = card.querySelector('.member-status');
if (statusElement) {
const statusClass = (member.status && member.status.toUpperCase() === 'ACTIVE') ? 'status-online' : 'status-offline';
const statusIcon = (member.status && member.status.toUpperCase() === 'ACTIVE') ? '🟢' : '🔴';
const statusIcon = (member.status && member.status.toUpperCase() === 'ACTIVE') ? window.icon('dotGreen', { width: 12, height: 12 }) : window.icon('dotRed', { width: 12, height: 12 });
statusElement.className = `member-status ${statusClass}`;
statusElement.innerHTML = `${statusIcon}`;
@@ -392,7 +392,7 @@ class ClusterMembersComponent extends Component {
logger.debug('ClusterMembersComponent: showEmptyState() called');
this.renderEmpty(`
<div class="empty-state">
<div class="empty-state-icon">🌐</div>
<div class="empty-state-icon">${window.icon('cluster', { width: 24, height: 24 })}</div>
<div>No cluster members found</div>
<div style="font-size: 0.9rem; margin-top: 0.5rem; opacity: 0.7;">
The cluster might be empty or not yet discovered
@@ -406,7 +406,7 @@ class ClusterMembersComponent extends Component {
const membersHTML = members.map(member => {
const statusClass = (member.status && member.status.toUpperCase() === 'ACTIVE') ? 'status-online' : 'status-offline';
const statusIcon = (member.status && member.status.toUpperCase() === 'ACTIVE') ? '🟢' : '🔴';
const statusIcon = (member.status && member.status.toUpperCase() === 'ACTIVE') ? window.icon('dotGreen', { width: 12, height: 12 }) : window.icon('dotRed', { width: 12, height: 12 });
logger.debug('ClusterMembersComponent: Rendering member:', member);

View File

@@ -20,19 +20,19 @@ class ClusterStatusComponent extends Component {
if (error) {
statusText = 'Cluster Error';
statusIcon = '❌';
statusIcon = window.icon('error', { width: 12, height: 12 });
statusClass = 'cluster-status-error';
} else if (totalNodes === 0) {
statusText = 'Cluster Offline';
statusIcon = '🔴';
statusIcon = window.icon('dotRed', { width: 12, height: 12 });
statusClass = 'cluster-status-offline';
} else if (clientInitialized) {
statusText = 'Cluster Online';
statusIcon = '🟢';
statusIcon = window.icon('dotGreen', { width: 12, height: 12 });
statusClass = 'cluster-status-online';
} else {
statusText = 'Cluster Connecting';
statusIcon = '🟡';
statusIcon = window.icon('dotYellow', { width: 12, height: 12 });
statusClass = 'cluster-status-connecting';
}

View File

@@ -357,7 +357,14 @@ class FirmwareComponent extends Component {
const progressHTML = `
<div class="firmware-upload-progress" id="firmware-upload-progress">
<div class="progress-header">
<h3>📤 Firmware Upload Progress</h3>
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16" style="margin-right:6px; vertical-align: -2px;">
<path d="M12 16V4"/>
<path d="M8 8l4-4 4 4"/>
<path d="M20 16v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2"/>
</svg>
Firmware Upload Progress
</h3>
<div class="progress-info">
<span>File: ${file.name}</span>
<span>Size: ${(file.size / 1024).toFixed(1)}KB</span>
@@ -470,20 +477,20 @@ class FirmwareComponent extends Component {
if (totalCount === 1) {
// Single node upload
if (successCount === 1) {
progressHeader.textContent = `📤 Firmware Upload Complete`;
progressSummary.innerHTML = `<span> Upload to ${results[0].hostname || results[0].nodeIp} completed successfully at ${new Date().toLocaleTimeString()}</span>`;
progressHeader.textContent = `Firmware Upload Complete`;
progressSummary.innerHTML = `<span>${window.icon('success', { width: 14, height: 14 })} Upload to ${results[0].hostname || results[0].nodeIp} completed successfully at ${new Date().toLocaleTimeString()}</span>`;
} else {
progressHeader.textContent = `📤 Firmware Upload Failed`;
progressSummary.innerHTML = `<span> Upload to ${results[0].hostname || results[0].nodeIp} failed at ${new Date().toLocaleTimeString()}</span>`;
progressHeader.textContent = `Firmware Upload Failed`;
progressSummary.innerHTML = `<span>${window.icon('error', { width: 14, height: 14 })} Upload to ${results[0].hostname || results[0].nodeIp} failed at ${new Date().toLocaleTimeString()}</span>`;
}
} else if (successCount === totalCount) {
// Multi-node upload - all successful
progressHeader.textContent = `📤 Firmware Upload Complete (${successCount}/${totalCount} Successful)`;
progressSummary.innerHTML = `<span> All uploads completed successfully at ${new Date().toLocaleTimeString()}</span>`;
progressHeader.textContent = `Firmware Upload Complete (${successCount}/${totalCount} Successful)`;
progressSummary.innerHTML = `<span>${window.icon('success', { width: 14, height: 14 })} All uploads completed successfully at ${new Date().toLocaleTimeString()}</span>`;
} else {
// Multi-node upload - some failed
progressHeader.textContent = `📤 Firmware Upload Results (${successCount}/${totalCount} Successful)`;
progressSummary.innerHTML = `<span>⚠️ Upload completed with ${totalCount - successCount} failure(s) at ${new Date().toLocaleTimeString()}</span>`;
progressHeader.textContent = `Firmware Upload Results (${successCount}/${totalCount} Successful)`;
progressSummary.innerHTML = `<span>${window.icon('warning', { width: 14, height: 14 })} Upload completed with ${totalCount - successCount} failure(s) at ${new Date().toLocaleTimeString()}</span>`;
}
}
}
@@ -682,7 +689,7 @@ class FirmwareComponent extends Component {
}
const html = `
<div class="affected-nodes">
<div class="progress-header"><h3>🎯 Affected Nodes (${nodes.length})</h3></div>
<div class="progress-header"><h3>Affected Nodes (${nodes.length})</h3></div>
<div class="progress-list">
${nodes.map(n => `
<div class="progress-item" data-node-ip="${n.ip}">

View File

@@ -236,7 +236,7 @@ class MonitoringViewComponent extends Component {
if (error) {
container.innerHTML = `
<div class="error">
<div>❌ Error: ${error}</div>
<div>${window.icon('error', { width: 14, height: 14, class: 'icon' })} Error: ${this.escapeHtml(String(error))}</div>
</div>
`;
return;
@@ -259,7 +259,7 @@ class MonitoringViewComponent extends Component {
<div class="summary-stats">
<div class="stat-card">
<div class="stat-icon">🖥️</div>
<div class="stat-icon">${window.icon('computer', { width: 18, height: 18 })}</div>
<div class="stat-content">
<div class="stat-label">Total Nodes</div>
<div class="stat-value">${clusterSummary.totalNodes}</div>
@@ -267,7 +267,7 @@ class MonitoringViewComponent extends Component {
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-icon">${window.icon('cpu', { width: 18, height: 18 })}</div>
<div class="stat-content">
<div class="stat-label">CPU</div>
<div class="stat-value">${Math.round(clusterSummary.totalCpu - clusterSummary.availableCpu)}MHz / ${Math.round(clusterSummary.totalCpu)}MHz</div>
@@ -281,7 +281,7 @@ class MonitoringViewComponent extends Component {
</div>
<div class="stat-card">
<div class="stat-icon">🧠</div>
<div class="stat-icon">${window.icon('memory', { width: 18, height: 18 })}</div>
<div class="stat-content">
<div class="stat-label">Memory</div>
<div class="stat-value">${this.viewModel.formatResourceValue(clusterSummary.totalMemory - clusterSummary.availableMemory, 'memory')} / ${this.viewModel.formatResourceValue(clusterSummary.totalMemory, 'memory')}</div>
@@ -295,7 +295,7 @@ class MonitoringViewComponent extends Component {
</div>
<div class="stat-card">
<div class="stat-icon">💾</div>
<div class="stat-icon">${window.icon('storage', { width: 18, height: 18 })}</div>
<div class="stat-content">
<div class="stat-label">Storage</div>
<div class="stat-value">${this.viewModel.formatResourceValue(clusterSummary.totalStorage - clusterSummary.availableStorage, 'storage')} / ${this.viewModel.formatResourceValue(clusterSummary.totalStorage, 'storage')}</div>
@@ -328,7 +328,7 @@ class MonitoringViewComponent extends Component {
if (error) {
container.innerHTML = `
<div class="error">
<div>❌ Error: ${error}</div>
<div>${window.icon('error', { width: 14, height: 14, class: 'icon' })} Error: ${this.escapeHtml(String(error))}</div>
</div>
`;
return;
@@ -357,7 +357,7 @@ class MonitoringViewComponent extends Component {
const nodeCount = nodeResources.size;
container.innerHTML = `
<div class="nodes-monitoring-content">
<h3>🖥️ Node Resource Details</h3>
<h3>${window.icon('computer', { width: 16, height: 16, class: 'icon', strokeWidth: 2 })} Node Resource Details</h3>
<div class="nodes-grid" data-item-count="${nodeCount}">
${nodesHtml}
</div>
@@ -378,7 +378,7 @@ class MonitoringViewComponent extends Component {
<div class="node-header">
<div class="status-hostname-group">
<div class="node-status-indicator status-offline">
🔴
${window.icon('dotRed', { width: 12, height: 12 })}
</div>
<div class="node-title">${hostname || ip}</div>
</div>
@@ -397,12 +397,12 @@ class MonitoringViewComponent extends Component {
` : ''}
<div class="node-error">
<div class="error-label">⚠️ Error</div>
<div class="error-label">${window.icon('warning', { width: 14, height: 14 })} Error</div>
<div class="error-message">${error || 'Monitoring endpoint not available'}</div>
</div>
${nodeData.lastSeen ? `
<div class="node-uptime">
<div class="uptime-label">⏱️ Last Seen</div>
<div class="uptime-label">${window.icon('timer', { width: 14, height: 14 })} Last Seen</div>
<div class="uptime-value">${this.formatLastSeen(nodeData.lastSeen)}</div>
</div>
` : ''}
@@ -457,8 +457,8 @@ class MonitoringViewComponent extends Component {
}
// Determine status indicator based on resource source
const statusIcon = resourceSource === 'monitoring' ? '🟢' :
resourceSource === 'basic' ? '🟡' : '🔴';
const statusIcon = resourceSource === 'monitoring' ? window.icon('dotGreen', { width: 12, height: 12 }) :
resourceSource === 'basic' ? window.icon('dotYellow', { width: 12, height: 12 }) : window.icon('dotRed', { width: 12, height: 12 });
const statusClass = resourceSource === 'monitoring' ? 'status-online' :
resourceSource === 'basic' ? 'status-warning' : 'status-offline';
@@ -487,27 +487,27 @@ class MonitoringViewComponent extends Component {
${system.uptime_formatted ? `
<div class="node-uptime">
<div class="uptime-label">⏱️ Uptime</div>
<div class="uptime-label">${window.icon('timer', { width: 14, height: 14 })} Uptime</div>
<div class="uptime-value">${system.uptime_formatted}</div>
</div>
` : ''}
<div class="node-latency">
<div class="latency-label">🐢 Latency</div>
<div class="latency-label">${window.icon('latency', { width: 14, height: 14 })} Latency</div>
<div class="latency-value">${nodeData.latency ? `${nodeData.latency}ms` : 'N/A'}</div>
</div>
<div class="latency-divider"></div>
${(nodeData.basic?.flashChipSize || nodeData.resources?.flashChipSize) ? `
<div class="node-flash">
<div class="flash-label">💾 Flash</div>
<div class="flash-label">${window.icon('storage', { width: 14, height: 14 })} Flash</div>
<div class="flash-value">${this.formatFlashSize(nodeData.basic?.flashChipSize || nodeData.resources?.flashChipSize)}</div>
</div>
` : ''}
<div class="node-resources">
<div class="resource-item">
<div class="resource-label"> CPU</div>
<div class="resource-label">${window.icon('cpu', { width: 14, height: 14 })} CPU</div>
<div class="resource-value">
<span class="value-label">Used:</span> ${Math.round(cpuUsed)}MHz / <span class="value-label">Total:</span> ${Math.round(cpuTotal)}MHz
</div>
@@ -520,7 +520,7 @@ class MonitoringViewComponent extends Component {
</div>
<div class="resource-item">
<div class="resource-label">🧠 Memory</div>
<div class="resource-label">${window.icon('memory', { width: 14, height: 14 })} Memory</div>
<div class="resource-value">
<span class="value-label">Used:</span> ${this.viewModel.formatResourceValue(memoryUsed, 'memory')} / <span class="value-label">Total:</span> ${this.viewModel.formatResourceValue(memoryTotal, 'memory')}
</div>
@@ -533,7 +533,7 @@ class MonitoringViewComponent extends Component {
</div>
<div class="resource-item">
<div class="resource-label">💾 Storage</div>
<div class="resource-label">${window.icon('storage', { width: 14, height: 14 })} Storage</div>
<div class="resource-value">
<span class="value-label">Used:</span> ${this.viewModel.formatResourceValue(storageUsed, 'storage')} / <span class="value-label">Total:</span> ${this.viewModel.formatResourceValue(storageTotal, 'storage')}
</div>

View File

@@ -516,7 +516,7 @@ class NodeDetailsComponent extends Component {
if (!endpoints || !Array.isArray(endpoints.endpoints) || endpoints.endpoints.length === 0) {
return `
<div class="no-endpoints">
<div>🧩 No endpoints reported</div>
<div>No endpoints reported</div>
<div style="font-size: 0.9rem; margin-top: 0.5rem; opacity: 0.7;">This node did not return any endpoints</div>
</div>
`;
@@ -668,14 +668,14 @@ class NodeDetailsComponent extends Component {
const pretty = typeof response?.data === 'object' ? JSON.stringify(response.data, null, 2) : String(response?.data ?? '');
resultEl.innerHTML = `
<div class="endpoint-call-success">
<div> Success</div>
<div>${window.icon('success', { width: 14, height: 14 })} Success</div>
<pre class="endpoint-result-pre">${this.escapeHtml(pretty)}</pre>
</div>
`;
} catch (err) {
resultEl.innerHTML = `
<div class="endpoint-call-error">
<div> Error: ${this.escapeHtml(err.message || 'Request failed')}</div>
<div>${window.icon('error', { width: 14, height: 14 })} Error: ${this.escapeHtml(err.message || 'Request failed')}</div>
</div>
`;
}
@@ -733,7 +733,7 @@ class NodeDetailsComponent extends Component {
const summaryHTML = summary ? `
<div class="tasks-summary">
<div class="tasks-summary-left">
<div class="summary-icon">📋</div>
<div class="summary-icon">${window.icon('file', { width: 16, height: 16 })}</div>
<div>
<div class="summary-title">Tasks Overview</div>
<div class="summary-subtitle">System task management and monitoring</div>
@@ -760,12 +760,12 @@ class NodeDetailsComponent extends Component {
<div class="task-header">
<span class="task-name">${task.name || 'Unknown Task'}</span>
<span class="task-status ${task.running ? 'running' : 'stopped'}">
${task.running ? '🟢 Running' : '🔴 Stopped'}
${task.running ? window.icon('dotGreen', { width: 10, height: 10 }) + ' Running' : window.icon('dotRed', { width: 10, height: 10 }) + ' Stopped'}
</span>
</div>
<div class="task-details">
<span class="task-interval">Interval: ${task.interval}ms</span>
<span class="task-enabled">${task.enabled ? '🟢 Enabled' : '🔴 Disabled'}</span>
<span class="task-enabled">${task.enabled ? window.icon('dotGreen', { width: 10, height: 10 }) + ' Enabled' : window.icon('dotRed', { width: 10, height: 10 }) + ' Disabled'}</span>
</div>
</div>
`).join('');
@@ -780,7 +780,7 @@ class NodeDetailsComponent extends Component {
return `
<div class="tasks-summary">
<div class="tasks-summary-left">
<div class="summary-icon">📋</div>
<div class="summary-icon">${window.icon('file', { width: 16, height: 16 })}</div>
<div>
<div class="summary-title">Tasks Overview</div>
<div class="summary-subtitle">${total > 0 ? `Total tasks: ${total}, active: ${active}` : 'This node has no running tasks'}</div>
@@ -802,7 +802,7 @@ class NodeDetailsComponent extends Component {
</div>
</div>
<div class="no-tasks">
<div>📋 No active tasks found</div>
<div>No active tasks found</div>
<div style="font-size: 0.9rem; margin-top: 0.5rem; opacity: 0.7;">
${total > 0 ? `Total tasks: ${total}, active: ${active}` : 'This node has no running tasks'}
</div>
@@ -818,7 +818,7 @@ class NodeDetailsComponent extends Component {
<div class="upload-area">
<input type="file" id="firmware-file" accept=".bin,.hex" style="display: none;">
<button class="upload-btn" data-action="select-file">
📁 Choose Firmware File
${window.icon('file', { width: 14, height: 14 })} Choose Firmware File
</button>
<div class="upload-info">Select a .bin or .hex file to upload</div>
<div id="upload-status" style="display: none;"></div>
@@ -879,15 +879,15 @@ class NodeDetailsComponent extends Component {
// Show upload status
uploadStatus.style.display = 'block';
uploadStatus.innerHTML = `
<div class="upload-progress">
<div>📤 Uploading ${file.name}...</div>
<div class="upload-progress">
<div>${window.icon('upload', { width: 14, height: 14 })} Uploading ${this.escapeHtml(file.name)}...</div>
<div style="font-size: 0.8rem; opacity: 0.7;">Size: ${(file.size / 1024).toFixed(1)}KB</div>
</div>
`;
// Disable upload button
uploadBtn.disabled = true;
uploadBtn.textContent = 'Uploading...';
uploadBtn.textContent = 'Uploading...';
// Get the member IP from the card if available, otherwise fallback to view model state
const memberCard = this.container.closest('.member-card');
@@ -908,7 +908,7 @@ class NodeDetailsComponent extends Component {
// Show success
uploadStatus.innerHTML = `
<div class="upload-success">
<div> Firmware uploaded successfully!</div>
<div>${window.icon('success', { width: 14, height: 14 })} Firmware uploaded successfully!</div>
<div style="font-size: 0.8rem; margin-top: 0.5rem; opacity: 0.7;">Node: ${memberIp}</div>
<div style="font-size: 0.8rem; margin-top: 0.5rem; opacity: 0.7;">Size: ${(file.size / 1024).toFixed(1)}KB</div>
</div>
@@ -922,7 +922,7 @@ class NodeDetailsComponent extends Component {
// Show error
uploadStatus.innerHTML = `
<div class="upload-error">
<div>❌ Upload failed: ${error.message}</div>
<div>${window.icon('error', { width: 14, height: 14 })} Upload failed: ${this.escapeHtml(error.message)}</div>
</div>
`;
} finally {

View File

@@ -28,7 +28,7 @@ class PrimaryNodeComponent extends Component {
const error = this.viewModel.get('error');
if (error) {
this.setText('#primary-node-ip', 'Discovery Failed');
this.setText('#primary-node-ip', 'Discovery Failed');
this.setClass('#primary-node-ip', 'error', true);
this.setClass('#primary-node-ip', 'discovering', false);
this.setClass('#primary-node-ip', 'selecting', false);
@@ -36,19 +36,19 @@ class PrimaryNodeComponent extends Component {
}
if (!primaryNode) {
this.setText('#primary-node-ip', '🔍 No Nodes Found');
this.setText('#primary-node-ip', 'No Nodes Found');
this.setClass('#primary-node-ip', 'error', true);
this.setClass('#primary-node-ip', 'discovering', false);
this.setClass('#primary-node-ip', 'selecting', false);
return;
}
const status = clientInitialized ? '' : '⚠️';
const status = clientInitialized ? '' : '';
const nodeCount = (onlineNodes && onlineNodes > 0)
? ` (${onlineNodes}/${totalNodes} online)`
: (totalNodes > 1 ? ` (${totalNodes} nodes)` : '');
this.setText('#primary-node-ip', `${status} ${primaryNode}${nodeCount}`);
this.setText('#primary-node-ip', `${primaryNode}${nodeCount}`);
this.setClass('#primary-node-ip', 'error', false);
this.setClass('#primary-node-ip', 'discovering', false);
this.setClass('#primary-node-ip', 'selecting', false);
@@ -57,7 +57,7 @@ class PrimaryNodeComponent extends Component {
async handleRandomSelection() {
try {
// Show selecting state
this.setText('#primary-node-ip', '🎲 Selecting...');
this.setText('#primary-node-ip', 'Selecting...');
this.setClass('#primary-node-ip', 'selecting', true);
this.setClass('#primary-node-ip', 'discovering', false);
this.setClass('#primary-node-ip', 'error', false);
@@ -65,7 +65,7 @@ class PrimaryNodeComponent extends Component {
await this.viewModel.selectRandomPrimaryNode();
// Show success briefly
this.setText('#primary-node-ip', '🎯 Selection Complete');
this.setText('#primary-node-ip', 'Selection Complete');
// Update display after delay
setTimeout(() => {
@@ -74,7 +74,7 @@ class PrimaryNodeComponent extends Component {
} catch (error) {
logger.error('Failed to select random primary node:', error);
this.setText('#primary-node-ip', 'Selection Failed');
this.setText('#primary-node-ip', 'Selection Failed');
this.setClass('#primary-node-ip', 'error', true);
this.setClass('#primary-node-ip', 'selecting', false);
this.setClass('#primary-node-ip', 'discovering', false);

View File

@@ -103,8 +103,16 @@
<div class="terminal-header">
<div class="terminal-title">Terminal</div>
<div class="terminal-actions">
<button class="terminal-minimize-btn" title="Minimize">_</button>
<button class="terminal-close-btn" title="Close">✕</button>
<button class="terminal-minimize-btn" title="Minimize" aria-label="Minimize">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
<path d="M5 19h14"/>
</svg>
</button>
<button class="terminal-close-btn" title="Close" aria-label="Close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<div class="terminal-body">