From cabc08de2944606f495cf931838d214a000a20ce Mon Sep 17 00:00:00 2001 From: 0x1d Date: Thu, 23 Oct 2025 08:06:55 +0200 Subject: [PATCH 1/6] fix: error styling and buttons --- public/styles/main.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/styles/main.css b/public/styles/main.css index e2d33d2..cef62c8 100644 --- a/public/styles/main.css +++ b/public/styles/main.css @@ -7504,6 +7504,8 @@ html { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); } +<<<<<<< Updated upstream +======= .overlay-dialog-btn-danger { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); @@ -7544,7 +7546,6 @@ html { .rollout-panel { padding: 1.5rem; - max-width: 600px; } .rollout-header { @@ -7836,3 +7837,4 @@ html { background: var(--error-bg); color: var(--error-color); } +>>>>>>> Stashed changes -- 2.49.1 From c6949c36c1e73e49422b6734f90011f5a809e18f Mon Sep 17 00:00:00 2001 From: 0x1d Date: Thu, 23 Oct 2025 11:25:44 +0200 Subject: [PATCH 2/6] feat: harmonize styling --- public/styles/main.css | 1039 ++++++++++++---------------------------- 1 file changed, 301 insertions(+), 738 deletions(-) diff --git a/public/styles/main.css b/public/styles/main.css index cef62c8..0ee9fda 100644 --- a/public/styles/main.css +++ b/public/styles/main.css @@ -9,6 +9,169 @@ box-sizing: border-box; } +/* ======================================== + STANDARDIZED BUTTON AND INPUT STYLES + ======================================== + Explicit button classes for better maintainability. + Apply .btn class to buttons that need standard styling. +*/ + +/* === Standard Button Style === */ +.btn, +.refresh-btn, +.config-btn, +.deploy-btn, +.upload-btn, +.upload-btn-compact, +.save-labels-btn, +.add-label-btn, +.add-btn, +.clear-filters-btn { + background: rgba(255, 255, 255, 0.1); + border: 1px solid var(--border-secondary); + color: var(--text-secondary); + border-radius: 6px; + padding: 0.4rem 0.8rem; + cursor: pointer; + font-size: 0.875rem; + font-weight: 500; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.4rem; + transition: all 0.2s ease; + height: fit-content; + min-height: 2rem; +} + +.btn:hover:not(:disabled), +.refresh-btn:hover:not(:disabled), +.config-btn:hover:not(:disabled), +.deploy-btn:hover:not(:disabled), +.upload-btn:hover:not(:disabled), +.upload-btn-compact:hover:not(:disabled), +.save-labels-btn:hover:not(:disabled), +.add-label-btn:hover:not(:disabled), +.add-btn:hover:not(:disabled), +.clear-filters-btn:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.15); + border-color: rgba(255, 255, 255, 0.25); + color: var(--text-primary); +} + +.btn:disabled, +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* === Icon-Only Button Style (Minimal) === */ +.btn-icon, +.theme-toggle, +.burger-btn, +.primary-node-refresh, +.filter-pill-remove, +.chip-remove, +.remove-label-btn, +.tab-close-btn, +.member-terminal-btn, +.tab-refresh-btn, +.drawer-terminal-btn, +.drawer-close, +.terminal-close-btn, +.terminal-minimize-btn, +.member-overlay-close, +.overlay-dialog-close { + background: none; + border: none; + color: var(--text-primary); + padding: 0.25rem; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + opacity: 0.7; +} + +.btn-icon:hover, +.theme-toggle:hover, +.burger-btn:hover, +.primary-node-refresh:hover, +.filter-pill-remove:hover, +.chip-remove:hover, +.remove-label-btn:hover, +.tab-close-btn:hover, +.member-terminal-btn:hover, +.tab-refresh-btn:hover, +.drawer-terminal-btn:hover, +.drawer-close:hover, +.terminal-close-btn:hover, +.terminal-minimize-btn:hover, +.member-overlay-close:hover, +.overlay-dialog-close:hover { + opacity: 1; +} + +/* === Navigation Button Style (No background/border) === */ +.nav-tab { + background: transparent; + border: 1px solid transparent; + color: var(--text-secondary); + padding: 0.5rem 1rem; + border-radius: 8px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 500; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + transition: all 0.2s ease; +} + +.nav-tab:hover { + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); +} + +.nav-tab.active { + background: rgba(255, 255, 255, 0.1); + border-color: var(--border-secondary); + color: var(--text-primary); +} + +/* === Base Form Input Style (Compact) === */ +.form-input, +input:not([type="checkbox"]):not([type="radio"]), +select, +textarea { + background: var(--bg-secondary); + border: 1px solid var(--border-secondary); + border-radius: 6px; + padding: 0.4rem 0.75rem; + color: var(--text-primary); + font-size: 0.875rem; + font-family: inherit; + transition: all 0.2s ease; + min-height: 2rem; +} + +.form-input:focus, +input:not([type="checkbox"]):not([type="radio"]):focus, +select:focus, +textarea:focus { + outline: none; + border-color: #60a5fa; + box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2); +} + +.form-input::placeholder, +input::placeholder, +textarea::placeholder { + color: var(--text-tertiary); +} + /* === SVG icon alignment and sizing tweaks === */ /* Align inline SVGs vertically with adjacent text */ .nav-tab > svg, @@ -105,9 +268,6 @@ p { align-items: center; gap: 0.4rem; padding: 0.4rem 0.75rem; - background: rgba(255, 255, 255, 0.05); - border-radius: 6px; - border: 1px solid rgba(255, 255, 255, 0.1); } .primary-node-label { @@ -246,9 +406,6 @@ p { align-items: center; gap: 0.5rem; padding: 0.4rem 0.75rem; - background: rgba(255, 255, 255, 0.05); - border-radius: 6px; - border: 1px solid rgba(255, 255, 255, 0.1); } .filter-label { @@ -258,25 +415,40 @@ p { } .filter-select { - background: rgba(255, 255, 255, 0.08); + background-color: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 4px; color: var(--text-primary); font-size: 0.85rem; - padding: 0.3rem 0.5rem; + padding: 0.3rem 2rem 0.3rem 0.75rem !important; min-width: 120px; cursor: pointer; transition: all 0.2s ease; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ffffff' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 12px; } .filter-select:hover { - background: rgba(255, 255, 255, 0.12); + background-color: rgba(255, 255, 255, 0.12); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ffffff' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 12px; border-color: rgba(255, 255, 255, 0.2); } .filter-select:focus { outline: none; - background: rgba(255, 255, 255, 0.15); + background-color: rgba(255, 255, 255, 0.15); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ffffff' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 12px; border-color: var(--accent-color); box-shadow: 0 0 0 2px rgba(var(--accent-color-rgb), 0.2); } @@ -300,28 +472,10 @@ p { font-weight: 600; } -.clear-filters-btn { - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.15); - border-radius: 4px; - color: var(--text-secondary); - padding: 0.3rem; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.clear-filters-btn:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.2); - color: var(--text-primary); -} - +/* Clear-filters-btn styling moved to standard button section */ +/* Hide clear filters button when disabled (no filters active) */ .clear-filters-btn:disabled { - opacity: 0.5; - cursor: not-allowed; + display: none; } .primary-node-refresh:active { @@ -337,36 +491,23 @@ p { border-bottom: 1px solid rgba(255, 255, 255, 0.08); } -.firmware-header-left { - /* Placeholder for future content if needed */ -} - -.refresh-btn { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - gap: 0.5rem; +/* Harmonized toolbar buttons - all inherit from base with consistent styling */ +.refresh-btn, +.config-btn, +.deploy-btn, +.add-btn, +.upload-btn-compact, +.clear-filters-btn { backdrop-filter: var(--backdrop-blur); - margin: 0; } -.refresh-btn:hover { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -.refresh-btn:active { - transform: translateY(0); +.refresh-btn:hover, +.config-btn:hover, +.deploy-btn:hover:not(:disabled), +.add-btn:hover:not(:disabled), +.upload-btn-compact:hover:not(:disabled), +.clear-filters-btn:hover:not(:disabled) { + transform: translateY(-1px); } .refresh-icon { @@ -381,12 +522,6 @@ p { transform: rotate(180deg); } -.refresh-btn:disabled { - opacity: 1; - cursor: not-allowed; - pointer-events: none; -} - .refresh-icon.spinning { animation: spin 1s linear infinite; } @@ -400,83 +535,9 @@ p { } } -/* Config Button */ -.config-btn { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - gap: 0.5rem; - backdrop-filter: var(--backdrop-blur); - margin: 0; -} - -.config-btn:hover { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -.config-btn:active { - transform: translateY(0); -} - -.config-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - pointer-events: none; -} - -.config-btn.loading { - opacity: 0.8; - cursor: not-allowed; -} - -/* Deploy Button */ -.deploy-btn { - background: linear-gradient(135deg, rgba(74, 222, 128, 0.2) 0%, rgba(74, 222, 128, 0.1) 100%); - border: 1px solid rgba(74, 222, 128, 0.3); - color: var(--accent-primary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -} - -.deploy-btn:hover { - background: linear-gradient(135deg, rgba(74, 222, 128, 0.3) 0%, rgba(74, 222, 128, 0.15) 100%); - border-color: rgba(74, 222, 128, 0.4); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(74, 222, 128, 0.2); -} - -.deploy-btn:active { - transform: translateY(0); -} - -.deploy-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - pointer-events: none; -} - +.config-btn.loading, .deploy-btn.loading { opacity: 0.8; - cursor: not-allowed; } /* WiFi Configuration Styles */ @@ -568,45 +629,20 @@ p { gap: 0.75rem; } +/* WiFi actions buttons use deploy-btn styling (green) */ .wifi-actions .config-btn { background: linear-gradient(135deg, rgba(74, 222, 128, 0.2) 0%, rgba(74, 222, 128, 0.1) 100%); - border: 1px solid rgba(74, 222, 128, 0.3); + border-color: rgba(74, 222, 128, 0.3); color: var(--accent-primary); - padding: 0.5rem 1rem; - border-radius: 8px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; } -.wifi-actions .config-btn:hover { +.wifi-actions .config-btn:hover:not(:disabled) { background: linear-gradient(135deg, rgba(74, 222, 128, 0.3) 0%, rgba(74, 222, 128, 0.15) 100%); border-color: rgba(74, 222, 128, 0.4); - transform: translateY(-1px); box-shadow: 0 2px 8px rgba(74, 222, 128, 0.2); } -.wifi-actions .config-btn:active { - transform: translateY(0); -} - -.wifi-actions .config-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - pointer-events: none; -} - -.wifi-actions .config-btn.loading { - opacity: 0.8; - cursor: not-allowed; -} - -/* Firmware Form Styles */ +/* Firmware Form Styles - inherits from base form input styles */ .firmware-form { margin-bottom: 2rem; } @@ -625,23 +661,6 @@ p { .firmware-form .form-group input { width: 100%; - padding: 0.75rem; - border: 1px solid var(--border-secondary); - border-radius: 8px; - background: var(--bg-secondary); - color: var(--text-primary); - font-size: 0.9rem; - transition: border-color 0.2s ease, box-shadow 0.2s ease; -} - -.firmware-form .form-group input:focus { - outline: none; - border-color: #60a5fa; - box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2); -} - -.firmware-form .form-group input::placeholder { - color: var(--text-tertiary); } .firmware-form .form-group input[readonly] { @@ -702,28 +721,10 @@ p { gap: 0.5rem; } +/* Label inputs inherit from base form-input styles */ .label-key-input, .label-value-input { flex: 1; - padding: 0.5rem 0.75rem; - background: var(--bg-secondary); - border: 1px solid var(--border-secondary); - border-radius: 6px; - color: var(--text-primary); - font-size: 0.9rem; - transition: border-color 0.2s ease, box-shadow 0.2s ease; -} - -.label-key-input:focus, -.label-value-input:focus { - outline: none; - border-color: #60a5fa; - box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2); -} - -.label-key-input::placeholder, -.label-value-input::placeholder { - color: var(--text-tertiary); } .label-separator { @@ -758,24 +759,7 @@ p { color: var(--text-primary); } -.remove-label-btn { - background: transparent; - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - border-radius: 4px; - padding: 0.25rem; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.remove-label-btn:hover { - background: rgba(239, 68, 68, 0.1); - border-color: #ef4444; - color: #ef4444; -} +/* Remove-label-btn styling moved to icon-only button section */ .firmware-form .firmware-actions { display: flex !important; @@ -787,38 +771,17 @@ p { border-top: 1px solid var(--border-primary); } +/* Firmware actions buttons inherit from base with slight customizations */ .firmware-actions .config-btn { - background: rgba(255, 255, 255, 0.1); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - gap: 0.5rem; backdrop-filter: var(--backdrop-blur); - margin: 0; } .firmware-actions .config-btn:hover { - background: rgba(255, 255, 255, 0.15); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -.firmware-actions .config-btn:active { - transform: translateY(0); + transform: translateY(-1px); } .firmware-actions .config-btn:disabled { opacity: 0.4; - cursor: not-allowed; - pointer-events: none; background: rgba(255, 255, 255, 0.05); color: var(--text-tertiary); border-color: var(--border-primary); @@ -1011,18 +974,12 @@ p { overflow: visible; /* Allow content to expand */ } -.member-card.expanded:hover { -} - .expand-icon:hover { background: rgba(255, 255, 255, 0.1); color: var(--text-secondary); border-color: rgba(255, 255, 255, 0.2); } -.expand-icon:hover svg { -} - .member-card.expanded .expand-icon { background: rgba(255, 255, 255, 0.15); border-color: rgba(255, 255, 255, 0.25); @@ -1553,11 +1510,11 @@ p { background: rgba(255, 255, 255, 0.08); border: 1px solid var(--border-secondary); color: var(--text-secondary); - padding: 0.5rem 1rem; - border-radius: 8px 8px 0 0; + padding: 0.4rem 0.8rem; + border-radius: 6px 6px 0 0; cursor: pointer; - font-size: 0.9rem; - transition: all 0.3s ease; + font-size: 0.875rem; + transition: all 0.2s ease; border-bottom: none; } @@ -1853,34 +1810,14 @@ p { background: var(--bg-tertiary); } +/* Upload button inherits from base button styles */ .upload-btn { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - gap: 0.5rem; backdrop-filter: var(--backdrop-blur); margin-bottom: 1rem; - margin: auto; - } .upload-btn:hover { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -.upload-btn:active { - transform: translateY(0); + transform: translateY(-1px); } .upload-info { @@ -1921,8 +1858,6 @@ p { } .upload-btn:disabled { - opacity: 1; - cursor: not-allowed; transform: none !important; } @@ -1990,51 +1925,7 @@ p { gap: 1rem; } -.nav-tab { - background: transparent; - border: none; - color: var(--text-muted); - padding: 0.875rem 1.75rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.95rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; -} - -.nav-tab::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - opacity: 0; - transition: opacity 0.3s ease; -} - -.nav-tab:hover { - color: var(--text-secondary); - transform: translateY(-1px); -} - -.nav-tab:hover::before { - opacity: 1; -} - -.nav-tab.active { - background: rgba(255, 255, 255, 0.15); - color: var(--text-primary); - box-shadow: 0 4px 20px rgba(255, 255, 255, 0.1); - transform: translateY(-1px); -} - -.nav-tab.active::before { - opacity: 0; -} +/* Navigation tabs use base nav-tab style defined at top of file */ /* Cluster Status */ .cluster-status { @@ -2185,29 +2076,7 @@ p { color: #f87171; } -.add-btn { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - gap: 0.5rem; - backdrop-filter: var(--backdrop-blur); - margin: 0; -} - -.add-btn:hover { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} +/* Add-btn styling moved to standard button section */ /* Firmware Content */ .firmware-content { @@ -2589,7 +2458,12 @@ p { gap: 0.25rem; } -.action-btn { +/* Firmware action buttons - harmonized to match rollout button */ +.action-btn, +.edit-btn, +.download-btn, +.delete-btn, +.rollout-btn { background: transparent; border: 1px solid var(--border-secondary); color: var(--text-secondary); @@ -2600,30 +2474,25 @@ p { display: flex; align-items: center; justify-content: center; + min-height: auto; } -.action-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); - border-color: var(--border-primary); +.action-btn:hover, +.edit-btn:hover, +.download-btn:hover, +.delete-btn:hover, +.rollout-btn:hover { + background: rgba(34, 197, 94, 0.1); + border-color: #22c55e; + color: #22c55e; } -.action-btn.edit-btn:hover { - background: rgba(59, 130, 246, 0.1); - border-color: #3b82f6; - color: #3b82f6; -} - -.action-btn.download-btn:hover { - background: rgba(96, 165, 250, 0.1); - border-color: var(--accent-secondary); - color: var(--accent-secondary); -} - -.action-btn.delete-btn:hover { - background: rgba(239, 68, 68, 0.1); - border-color: #ef4444; - color: #ef4444; +.action-btn:active, +.edit-btn:active, +.download-btn:active, +.delete-btn:active, +.rollout-btn:active { + transform: translateY(0); } .firmware-card-body { @@ -2966,34 +2835,11 @@ p { margin-top: 0.25rem; } +/* Upload button compact - inherits from standard button section */ .upload-btn-compact { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - gap: 0.5rem; - backdrop-filter: var(--backdrop-blur); white-space: nowrap; } -.upload-btn-compact:hover { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -.upload-btn-compact:active { - transform: translateY(0); -} - .file-info { color: var(--text-tertiary); font-size: 0.9rem; @@ -3182,65 +3028,7 @@ p { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } -.deploy-btn { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - gap: 0.5rem; - backdrop-filter: var(--backdrop-blur); - min-width: 100px; - position: relative; - overflow: hidden; - white-space: nowrap; -} - -.deploy-btn::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); - transition: left 0.5s ease; -} - -.deploy-btn:hover:not(:disabled)::before { - left: 100%; -} - -.deploy-btn:hover:not(:disabled) { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -.deploy-btn:active:not(:disabled) { - transform: translateY(0); -} - -.deploy-btn:disabled { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - color: rgba(255, 255, 255, 0.4); - cursor: not-allowed; - transform: none; - box-shadow: none; -} - -.deploy-btn.loading { - background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%); - color: #1f2937; - cursor: not-allowed; -} +/* Deploy button styling moved to harmonized toolbar buttons section at top of file */ /* Responsive design for smaller screens */ @media (max-width: 768px) { @@ -3665,28 +3453,16 @@ p { } .clear-btn, -.refresh-btn { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.75rem 1.25rem; - border-radius: 12px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} +/* Refresh button styling moved to harmonized toolbar buttons section at top of file */ -.clear-btn:hover, -.refresh-btn:hover { +.clear-btn:hover { background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); border-color: rgba(255, 255, 255, 0.25); transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); } -.clear-btn:active, -.refresh-btn:active { +.clear-btn:active { transform: translateY(0); } @@ -3759,17 +3535,7 @@ p { } /* Navigation tab transitions */ -.nav-tab { - transition: all 0.2s ease; -} - -.nav-tab:hover { - transform: translateY(-1px); -} - -.nav-tab.active { - transform: translateY(0); -} +/* Navigation tabs use base nav-tab style defined at top of file */ .specific-node-option { gap: 0.5rem; @@ -4320,22 +4086,20 @@ select.param-input:focus { border: 1px solid rgba(59, 130, 246, 0.55); } +/* Label chip remove button inherits from icon-only button base style */ .label-chip .chip-remove { - appearance: none; - background: transparent; - border: none; - color: rgba(255, 255, 255, 0.85); - cursor: pointer; font-size: 0.85rem; line-height: 1; padding: 0 0.25rem; border-radius: 9999px; - transition: background 0.15s ease, transform 0.15s ease; } -.label-chip .chip-remove:hover { - background: rgba(255, 255, 255, 0.12); - transform: scale(1.05); +.label-chip .chip-remove svg, +.remove-label-btn svg { + width: 14px; + height: 14px; + stroke: currentColor; + stroke-width: 2; } @media (max-width: 768px) { @@ -4650,27 +4414,12 @@ select.param-input:focus { font-family: 'Courier New', monospace; } -.member-overlay-close { - background: none; - border: none; - color: var(--text-muted); - cursor: pointer; - padding: 8px; - border-radius: 8px; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.member-overlay-close:hover { - background: rgba(255, 255, 255, 0.1); - color: var(--text-secondary); -} - +/* Member overlay close uses base icon-only style */ .member-overlay-close svg { - width: 20px; - height: 20px; + width: 18px; + height: 18px; + stroke: currentColor; + stroke-width: 2; } @@ -4784,37 +4533,14 @@ select.param-input:focus { align-items: center; gap: 0.5rem; } -.drawer-terminal-btn { - background: transparent; - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - border-radius: 8px; - padding: 0.35rem; - cursor: pointer; -} -.drawer-terminal-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); -} -.drawer-terminal-btn svg { +/* Drawer buttons use base icon-only style */ +.drawer-terminal-btn svg, +.drawer-close svg { width: 18px; height: 18px; stroke: currentColor; stroke-width: 2; } -.drawer-close { - background: transparent; - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - border-radius: 8px; - padding: 0.35rem; - cursor: pointer; -} -.drawer-close:hover { - background: var(--bg-hover); - color: var(--text-primary); -} -.drawer-close svg { width: 18px; height: 18px; } .details-drawer-content { padding: 1rem; @@ -4868,73 +4594,42 @@ select.param-input:focus { flex-wrap: wrap; } -.upload-btn-compact { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - padding: 0.5rem 1rem; - border-radius: 8px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -} - -.upload-btn-compact:hover { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); -} - -/* Compact Deploy Button */ +/* Compact Deploy Button - green accent for important action */ .deploy-btn-compact { - background: linear-gradient(135deg, rgba(74, 222, 128, 0.2) 0%, rgba(74, 222, 128, 0.1) 100%); - border: 1px solid rgba(74, 222, 128, 0.3); - color: var(--accent-primary); - padding: 0.5rem 1rem; - border-radius: 8px; + background: rgba(34, 197, 94, 0.15); + border: 1px solid rgba(34, 197, 94, 0.4); + color: #22c55e; + border-radius: 6px; + padding: 0.4rem 0.8rem; cursor: pointer; - font-size: 0.9rem; + font-size: 0.875rem; font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; + display: inline-flex; align-items: center; justify-content: center; - gap: 0.5rem; + gap: 0.4rem; + transition: all 0.2s ease; + height: fit-content; + min-height: 2rem; } -.deploy-btn-compact:hover { - background: linear-gradient(135deg, rgba(74, 222, 128, 0.3) 0%, rgba(74, 222, 128, 0.15) 100%); - border-color: rgba(74, 222, 128, 0.4); +.deploy-btn-compact:hover:not(:disabled) { + background: rgba(34, 197, 94, 0.25); + border-color: #22c55e; + color: #22c55e; transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(74, 222, 128, 0.2); -} - -.deploy-btn-compact:active { - transform: translateY(0); } .deploy-btn-compact:disabled { opacity: 0.5; cursor: not-allowed; - pointer-events: none; } .deploy-btn-compact.loading { opacity: 0.8; - cursor: not-allowed; } -.file-info { - font-size: 0.9rem; - color: var(--text-tertiary); - font-style: italic; -} +/* File info styling consolidated at line 2830 */ .file-info.has-file { color: var(--text-secondary); @@ -5554,24 +5249,11 @@ select.param-input:focus { display: flex; gap: 0.5rem; } -.terminal-actions button { - background: transparent; - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - border-radius: 6px; - padding: 0.25rem 0.5rem; - cursor: pointer; -} -.terminal-actions button:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - +/* Terminal action buttons use base icon-only style */ .terminal-actions .terminal-minimize-btn { - width: 2rem; - padding: 0.25rem 0; font-size: 1rem; line-height: 1; + font-weight: 400; } .terminal-body { @@ -6042,8 +5724,16 @@ select.param-input:focus { .filter-select { width: 100%; min-width: auto; - padding: 0.5rem; + padding: 0.5rem 2rem 0.5rem 0.75rem !important; font-size: 0.9rem; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-color: rgba(255, 255, 255, 0.08); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ffffff' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 12px; } .clear-filters-btn { @@ -6303,6 +5993,7 @@ html { .refresh-btn, .progress-item, .result-item, +/* File info styling consolidated at line 2830 */ .file-info { -webkit-tap-highlight-color: transparent; } @@ -6524,14 +6215,6 @@ html { } /* Cluster Summary Styles */ -.cluster-summary-content { - /* - background: var(--bg-tertiary); - border-radius: 12px; - padding: 1.5rem; - border: 1px solid var(--border-primary);*/ -} - .summary-header { display: flex; justify-content: space-between; @@ -6954,10 +6637,6 @@ html { } /* Monitoring Section in Node Details Drawer */ -.details-drawer .monitoring-section { - -} - .details-drawer .monitoring-section .monitoring-header { color: var(--text-primary); font-size: 1rem; @@ -7234,35 +6913,13 @@ html { stroke-width: 2; } +/* Add label button and save labels button inherit from base button styles */ .add-label-btn { - background: rgba(255, 255, 255, 0.1); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - border-radius: 8px; - padding: 0.5rem 1rem; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - display: flex; - align-items: center; - gap: 0.5rem; - transition: all 0.2s ease; - height: 2.5rem; align-self: flex-end; } -.add-label-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.15); - border-color: rgba(255, 255, 255, 0.25); - color: var(--text-primary); -} - -.add-label-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.add-label-btn svg { +.add-label-btn svg, +.save-labels-btn svg { width: 16px; height: 16px; stroke: currentColor; @@ -7277,41 +6934,10 @@ html { border-top: 1px solid var(--border-secondary); } -.save-labels-btn { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - border-radius: 8px; - padding: 0.5rem 1rem; - cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - display: flex; - align-items: center; - gap: 0.5rem; - transition: all 0.2s ease; -} - .save-labels-btn:hover:not(:disabled) { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 100%); - border-color: rgba(255, 255, 255, 0.25); - color: var(--text-primary); transform: translateY(-1px); } -.save-labels-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; -} - -.save-labels-btn svg { - width: 16px; - height: 16px; - stroke: currentColor; - stroke-width: 2; -} - .labels-message { padding: 0.75rem 1rem; border-radius: 8px; @@ -7431,23 +7057,7 @@ html { margin: 0; } -.overlay-dialog-close { - background: none; - border: none; - color: rgba(255, 255, 255, 0.6); - cursor: pointer; - padding: 8px; - border-radius: 8px; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.overlay-dialog-close:hover { - background: rgba(255, 255, 255, 0.1); - color: #ecf0f1; -} +/* Overlay dialog close uses base icon-only style */ .overlay-dialog-body { padding: 16px 24px; @@ -7504,8 +7114,6 @@ html { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); } -<<<<<<< Updated upstream -======= .overlay-dialog-btn-danger { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); @@ -7521,28 +7129,7 @@ html { } /* === Rollout Component Styles === */ -.rollout-btn { - background: transparent; - border: 1px solid var(--border-secondary); - color: var(--text-secondary); - border-radius: 6px; - padding: 0.375rem; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.rollout-btn:hover { - background: rgba(34, 197, 94, 0.1); - border-color: #22c55e; - color: #22c55e; -} - -.rollout-btn:active { - transform: translateY(0); -} +/* Rollout button styling moved to harmonized firmware action buttons section */ .rollout-panel { padding: 1.5rem; @@ -7682,30 +7269,24 @@ html { border-top: 1px solid var(--border-primary); } -/* Rollout confirm button - make it look important */ +/* Rollout confirm button - important action with green accent */ .rollout-actions .deploy-btn { - background: linear-gradient(135deg, rgba(74, 222, 128, 0.2) 0%, rgba(74, 222, 128, 0.1) 100%); - border: 1px solid rgba(74, 222, 128, 0.3); - color: #41cb6d; - font-weight: 500; - padding: 0.75rem 1.25rem; - font-size: 0.9rem; - border-radius: 12px; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + background: rgba(34, 197, 94, 0.15); + border: 1px solid rgba(34, 197, 94, 0.4); + color: #22c55e; } -.rollout-actions .deploy-btn:hover { - background: linear-gradient(135deg, rgba(74, 222, 128, 0.3) 0%, rgba(74, 222, 128, 0.15) 100%); - border-color: rgba(74, 222, 128, 0.4); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(74, 222, 128, 0.2); +.rollout-actions .deploy-btn:hover:not(:disabled) { + background: rgba(34, 197, 94, 0.25); + border-color: #22c55e; + color: #22c55e; + transform: translateY(-1px); } .rollout-actions .deploy-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; - box-shadow: none; } /* Rollout Progress Overlay */ @@ -7794,23 +7375,6 @@ html { background: var(--bg-tertiary); } -.rollout-node-item { - padding: 0.75rem; - border-bottom: 1px solid var(--border-primary); - display: flex; - justify-content: space-between; - align-items: center; -} - -.rollout-node-item:last-child { - border-bottom: none; -} - -.rollout-node-ip { - font-weight: 600; - color: var(--text-primary); -} - .rollout-node-status { padding: 0.25rem 0.5rem; border-radius: 4px; @@ -7837,4 +7401,3 @@ html { background: var(--error-bg); color: var(--error-color); } ->>>>>>> Stashed changes -- 2.49.1 From 531ddbee85d25ef2e0e490e2e3110f7c78dd484f Mon Sep 17 00:00:00 2001 From: 0x1d Date: Thu, 23 Oct 2025 11:38:03 +0200 Subject: [PATCH 3/6] feat: introduce routing system --- index.js | 14 +++++----- public/index.html | 16 +++++------ public/scripts/framework.js | 54 +++++++++++++++++++++++++++++++++++-- public/styles/main.css | 1 + 4 files changed, 69 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 21b13de..5a03a94 100644 --- a/index.js +++ b/index.js @@ -19,12 +19,7 @@ const PORT = process.env.PORT || 3000; // Serve static files from public directory app.use(express.static(path.join(__dirname, 'public'))); -// Serve the main HTML page -app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'index.html')); -}); - -// Health check endpoint +// Health check endpoint (before catch-all route) app.get('/health', (req, res) => { res.json({ status: 'healthy', @@ -34,6 +29,13 @@ app.get('/health', (req, res) => { }); }); +// SPA catch-all route - serves index.html for all routes +// This allows client-side routing to work properly +// Using regex pattern for Express 5 compatibility +app.get(/^\/(?!health$).*/, (req, res) => { + res.sendFile(path.join(__dirname, 'public', 'index.html')); +}); + // Start the server app.listen(PORT, '0.0.0.0', () => { console.log(`SPORE UI Frontend Server is running on http://0.0.0.0:${PORT}`); diff --git a/public/index.html b/public/index.html index b54c48b..9bc94de 100644 --- a/public/index.html +++ b/public/index.html @@ -18,7 +18,7 @@