diff --git a/public/index.html b/public/index.html index c43be27..dc0e1a8 100644 --- a/public/index.html +++ b/public/index.html @@ -18,10 +18,37 @@ @@ -44,7 +71,7 @@ Primary Node: Discovering... No file selected - + @@ -143,7 +187,12 @@
-

📡 Monitoring

+

+ + + + Monitoring +

Select a .bin or .hex file to upload
@@ -879,15 +879,15 @@ class NodeDetailsComponent extends Component { // Show upload status uploadStatus.style.display = 'block'; uploadStatus.innerHTML = ` -
-
📤 Uploading ${file.name}...
+
+
${window.icon('upload', { width: 14, height: 14 })} Uploading ${this.escapeHtml(file.name)}...
Size: ${(file.size / 1024).toFixed(1)}KB
`; // 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 = `
-
✅ Firmware uploaded successfully!
+
${window.icon('success', { width: 14, height: 14 })} Firmware uploaded successfully!
Node: ${memberIp}
Size: ${(file.size / 1024).toFixed(1)}KB
@@ -922,7 +922,7 @@ class NodeDetailsComponent extends Component { // Show error uploadStatus.innerHTML = `
-
❌ Upload failed: ${error.message}
+
${window.icon('error', { width: 14, height: 14 })} Upload failed: ${this.escapeHtml(error.message)}
`; } finally { diff --git a/public/scripts/components/PrimaryNodeComponent.js b/public/scripts/components/PrimaryNodeComponent.js index 61da853..bd5700e 100644 --- a/public/scripts/components/PrimaryNodeComponent.js +++ b/public/scripts/components/PrimaryNodeComponent.js @@ -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); diff --git a/public/scripts/components/TerminalPanelComponent.js b/public/scripts/components/TerminalPanelComponent.js index e563191..0d4c685 100644 --- a/public/scripts/components/TerminalPanelComponent.js +++ b/public/scripts/components/TerminalPanelComponent.js @@ -103,8 +103,16 @@
Terminal
- - + +
diff --git a/public/scripts/icons.js b/public/scripts/icons.js new file mode 100644 index 0000000..3f2efdb --- /dev/null +++ b/public/scripts/icons.js @@ -0,0 +1,61 @@ +// Centralized SVG Icons for SPORE UI +// Usage: window.icon('cluster', {class: 'foo', width: 16, height: 16}) -> returns inline SVG string +(function(){ + const toAttrs = (opts) => { + if (!opts) return ''; + const attrs = []; + if (opts.class) attrs.push(`class="${opts.class}"`); + if (opts.width) attrs.push(`width="${opts.width}"`); + if (opts.height) attrs.push(`height="${opts.height}"`); + if (opts.strokeWidth) attrs.push(`stroke-width="${opts.strokeWidth}"`); + return attrs.join(' '); + }; + + const withSvg = (inner, opts) => { + const attr = toAttrs(opts); + return `${inner}`; + }; + + const Icons = { + // Navigation / sections + cluster: (o) => withSvg(``, o), + topology: (o) => withSvg(``, o), + monitoring: (o) => withSvg(``, o), + firmware: (o) => withSvg(``, o), + + // Status / feedback + success: (o) => withSvg(``, o), + warning: (o) => withSvg(``, o), + error: (o) => withSvg(``, o), + offlineDot: (o) => withSvg(``, o), + dotGreen: (o) => withSvg(``, o), + dotYellow: (o) => withSvg(``, o), + dotRed: (o) => withSvg(``, o), + + // Actions + refresh: (o) => withSvg(``, o), + terminal: (o) => withSvg(``, o), + chevronDown: (o) => withSvg(``, o), + upload: (o) => withSvg(``, o), + file: (o) => withSvg(``, o), + timer: (o) => withSvg(``, o), + cpu: (o) => withSvg(``, o), + memory: (o) => withSvg(``, o), + storage: (o) => withSvg(``, o), + computer: (o) => withSvg(``, o), + latency: (o) => withSvg(``, o) + }; + + function icon(name, opts){ + const fn = Icons[name]; + if (!fn) return ''; + return fn(Object.assign({ width: 16, height: 16, strokeWidth: 2 }, opts || {})); + } + + if (typeof window !== 'undefined') { + window.Icons = Icons; + window.icon = icon; + } +})(); + + diff --git a/public/styles/main.css b/public/styles/main.css index 7b4089c..326915e 100644 --- a/public/styles/main.css +++ b/public/styles/main.css @@ -4,6 +4,35 @@ box-sizing: border-box; } +/* === SVG icon alignment and sizing tweaks === */ +/* Align inline SVGs vertically with adjacent text */ +.nav-tab > svg, +.theme-toggle > svg, +.burger-btn > svg, +h2 > svg, +h3 > svg, +.stat-icon > svg, +.resource-label > svg, +.flash-label > svg, +.uptime-label > svg, +.latency-label > svg, +.member-status > svg, +.tab-refresh-btn > svg, +.primary-node-info svg, +.upload-btn-compact > svg, +.deploy-btn > svg, +.upload-btn > svg, +.terminal-actions svg, +.terminal-dock-icon > svg { + vertical-align: middle; +} + +/* Monitoring view: slightly larger summary stat icons */ +.summary-stats .stat-icon > svg { + width: 22px; + height: 22px; +} + body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background: var(--bg-primary); @@ -767,16 +796,17 @@ p { flex-shrink: 0; } -.member-status { +.member-status, +.node-status-indicator { display: inline-flex; align-items: center; justify-content: center; - padding: 0.2rem; + width: 1rem; + height: 1rem; + padding: 0; border-radius: 50%; - font-size: 0.9rem; + font-size: 0; /* prevent line-height affecting size */ flex-shrink: 0; - min-width: 1.2rem; - min-height: 1.2rem; } .status-online { @@ -790,12 +820,22 @@ p { background: rgba(244, 67, 54, 0.3); color: #f44336; border: 1px solid rgba(244, 67, 54, 0.5); + border-radius: 50%; } .status-inactive { background: rgba(255, 152, 0, 0.3); color: #ff9800; border: 1px solid rgba(255, 152, 0, 0.5); + border-radius: 50%; +} + +/* Ensure the inner dot SVG fits nicely */ +.member-status > svg, +.node-status-indicator > svg { + width: 8px; + height: 8px; + display: block; } .member-latency { @@ -3452,6 +3492,7 @@ select.param-input:focus { padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--border-secondary); background: var(--bg-secondary); + border-radius: 12px 12px 0 0; } .terminal-title { font-weight: 600;