+
+
+
-
-
-
Metrics Information
-
- - Last Run Time: Timestamp of last scheduler run
- - Queued Tasks: Number of tasks in queue
- - Scheduled Tasks: Number of scheduled tasks
- - Running Tasks: Number of currently running tasks
- - Online CPUs: Number of available CPU cores
- - User Dispatches: Number of user-space dispatches
- - Kernel Dispatches: Number of kernel-space dispatches
- - Cancel Dispatches: Number of cancelled dispatches
- - Bounce Dispatches: Number of bounced dispatches
- - Failed Dispatches: Number of failed dispatches
- - Scheduler Congested: Number of scheduler congestion events
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
diff --git a/web/static/logo.png b/web/static/logo.png
new file mode 100644
index 0000000..97fc72e
Binary files /dev/null and b/web/static/logo.png differ
diff --git a/web/static/server.py b/web/static/server.py
new file mode 100644
index 0000000..028bd46
--- /dev/null
+++ b/web/static/server.py
@@ -0,0 +1,190 @@
+#!/usr/bin/env python3
+"""
+Gthulhu Web UI Development Server with API Proxy
+Serves static files and proxies API requests to avoid CORS issues.
+"""
+
+import http.server
+import urllib.request
+import urllib.error
+import json
+import os
+import sys
+
+# Configuration
+STATIC_DIR = os.path.dirname(os.path.abspath(__file__))
+API_BASE_URL = os.environ.get('API_BASE_URL', 'http://localhost:8080')
+DECISION_MAKER_URL = os.environ.get('DECISION_MAKER_URL', 'http://localhost:8081')
+PORT = int(os.environ.get('PORT', '3000'))
+
+# Endpoints that should be routed to the Decision Maker instead of Manager
+DECISION_MAKER_ENDPOINTS = [
+ '/api/v1/pods/pids',
+]
+
+class ProxyHandler(http.server.SimpleHTTPRequestHandler):
+ """HTTP handler that serves static files and proxies API requests."""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, directory=STATIC_DIR, **kwargs)
+
+ def do_OPTIONS(self):
+ """Handle CORS preflight requests."""
+ self.send_response(204)
+ self.send_cors_headers()
+ self.end_headers()
+
+ def do_GET(self):
+ """Handle GET requests - proxy API or serve static files."""
+ if self.path.startswith('/api/') or self.path.startswith('/health') or self.path.startswith('/version'):
+ self.proxy_request('GET', self._get_backend_url())
+ else:
+ super().do_GET()
+
+ def _get_backend_url(self):
+ """Determine which backend to use based on the request path."""
+ path_without_query = self.path.split('?')[0]
+ for endpoint in DECISION_MAKER_ENDPOINTS:
+ if path_without_query.startswith(endpoint):
+ return DECISION_MAKER_URL
+ return API_BASE_URL
+
+ def do_POST(self):
+ """Handle POST requests - proxy to API."""
+ self.proxy_request('POST', self._get_backend_url())
+
+ def do_PUT(self):
+ """Handle PUT requests - proxy to API."""
+ self.proxy_request('PUT', self._get_backend_url())
+
+ def do_DELETE(self):
+ """Handle DELETE requests - proxy to API."""
+ self.proxy_request('DELETE', self._get_backend_url())
+
+ def send_cors_headers(self):
+ """Add CORS headers to response."""
+ self.send_header('Access-Control-Allow-Origin', '*')
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
+ self.send_header('Access-Control-Max-Age', '86400')
+
+ def proxy_request(self, method, backend_url=None):
+ """Proxy request to the appropriate backend server."""
+ if backend_url is None:
+ backend_url = API_BASE_URL
+ url = f"{backend_url}{self.path}"
+
+ # Read request body for POST/PUT
+ body = None
+ if method in ('POST', 'PUT'):
+ content_length = int(self.headers.get('Content-Length', 0))
+ if content_length > 0:
+ body = self.rfile.read(content_length)
+
+ # Build request headers
+ headers = {}
+ if 'Content-Type' in self.headers:
+ headers['Content-Type'] = self.headers['Content-Type']
+ if 'Authorization' in self.headers:
+ headers['Authorization'] = self.headers['Authorization']
+
+ try:
+ req = urllib.request.Request(url, data=body, headers=headers, method=method)
+ with urllib.request.urlopen(req, timeout=30) as response:
+ response_body = response.read()
+
+ self.send_response(response.status)
+ self.send_cors_headers()
+
+ # Forward content-type
+ content_type = response.headers.get('Content-Type', 'application/json')
+ self.send_header('Content-Type', content_type)
+ self.send_header('Content-Length', len(response_body))
+ self.end_headers()
+
+ self.wfile.write(response_body)
+
+ except urllib.error.HTTPError as e:
+ error_body = e.read()
+ self.send_response(e.code)
+ self.send_cors_headers()
+ self.send_header('Content-Type', 'application/json')
+ self.send_header('Content-Length', len(error_body))
+ self.end_headers()
+ self.wfile.write(error_body)
+
+ except urllib.error.URLError as e:
+ error_msg = json.dumps({
+ 'success': False,
+ 'error': f'API server unreachable: {str(e.reason)}'
+ }).encode()
+ self.send_response(503)
+ self.send_cors_headers()
+ self.send_header('Content-Type', 'application/json')
+ self.send_header('Content-Length', len(error_msg))
+ self.end_headers()
+ self.wfile.write(error_msg)
+
+ except Exception as e:
+ error_msg = json.dumps({
+ 'success': False,
+ 'error': f'Proxy error: {str(e)}'
+ }).encode()
+ self.send_response(500)
+ self.send_cors_headers()
+ self.send_header('Content-Type', 'application/json')
+ self.send_header('Content-Length', len(error_msg))
+ self.end_headers()
+ self.wfile.write(error_msg)
+
+ def log_message(self, format, *args):
+ """Custom log format."""
+ method = args[0].split()[0] if args else '?'
+ path = args[0].split()[1] if args and len(args[0].split()) > 1 else '?'
+ status = args[1] if len(args) > 1 else '?'
+
+ # Color coding
+ if str(status).startswith('2'):
+ color = '\033[92m' # Green
+ elif str(status).startswith('3'):
+ color = '\033[93m' # Yellow
+ elif str(status).startswith('4'):
+ color = '\033[91m' # Red
+ elif str(status).startswith('5'):
+ color = '\033[95m' # Magenta
+ else:
+ color = '\033[0m' # Default
+
+ reset = '\033[0m'
+ print(f"{color}[{method}]{reset} {path} → {color}{status}{reset}")
+
+
+def main():
+ print(f"""
+\033[96m╔═══════════════════════════════════════════════════════════╗
+║ Gthulhu Web UI Development Server ║
+╚═══════════════════════════════════════════════════════════╝\033[0m
+
+ \033[92m●\033[0m Static files: {STATIC_DIR}
+ \033[92m●\033[0m Manager API: {API_BASE_URL}
+ \033[92m●\033[0m DecisionMaker: {DECISION_MAKER_URL}
+ \033[92m●\033[0m Server: http://localhost:{PORT}
+
+ \033[93mPress Ctrl+C to stop\033[0m
+""")
+
+ # Use ThreadingHTTPServer to handle multiple concurrent requests
+ class ThreadingHTTPServer(http.server.ThreadingHTTPServer):
+ allow_reuse_address = True
+ daemon_threads = True
+
+ with ThreadingHTTPServer(('', PORT), ProxyHandler) as httpd:
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print("\n\033[93mShutting down...\033[0m")
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/web/static/start-dev.sh b/web/static/start-dev.sh
new file mode 100755
index 0000000..3920ec5
--- /dev/null
+++ b/web/static/start-dev.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+# Gthulhu Web UI Development Environment Startup Script
+
+set -e
+
+echo "╔═══════════════════════════════════════════════════════════╗"
+echo "║ Gthulhu Development Environment Setup ║"
+echo "╚═══════════════════════════════════════════════════════════╝"
+echo ""
+
+# Colors
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+CYAN='\033[0;36m'
+NC='\033[0m' # No Color
+
+# Get current scheduler pod name (it changes on restart)
+SCHEDULER_POD=$(microk8s kubectl get pods 2>/dev/null | grep gthulhu-scheduler | awk '{print $1}')
+
+if [ -z "$SCHEDULER_POD" ]; then
+ echo -e "${YELLOW}⚠ Warning: Could not find gthulhu-scheduler pod${NC}"
+ SCHEDULER_POD="gthulhu-scheduler-XXXXX"
+fi
+
+echo -e "${GREEN}✓ Found scheduler pod: ${SCHEDULER_POD}${NC}"
+echo ""
+echo -e "${CYAN}Please open 3 terminals and run these commands:${NC}"
+echo ""
+echo "┌─────────────────────────────────────────────────────────────────────────────┐"
+echo "│ Terminal 1 - Manager API (Port 8080) │"
+echo "├─────────────────────────────────────────────────────────────────────────────┤"
+echo -e "│ ${GREEN}microk8s kubectl port-forward svc/gthulhu-manager 8080:8080 --address 0.0.0.0${NC} │"
+echo "│ Purpose: Auth, Users, Intents, Strategies APIs │"
+echo "└─────────────────────────────────────────────────────────────────────────────┘"
+echo ""
+echo "┌─────────────────────────────────────────────────────────────────────────────┐"
+echo "│ Terminal 2 - Decision Maker API (Port 8081) │"
+echo "├─────────────────────────────────────────────────────────────────────────────┤"
+echo -e "│ ${GREEN}microk8s kubectl port-forward pod/${SCHEDULER_POD} 8081:8080 --address 0.0.0.0${NC}"
+echo "│ Purpose: Pod-PID Mapping API (scans /proc filesystem) │"
+echo "└─────────────────────────────────────────────────────────────────────────────┘"
+echo ""
+echo "┌─────────────────────────────────────────────────────────────────────────────┐"
+echo "│ Terminal 3 - Web UI Dev Server (Port 3000) │"
+echo "├─────────────────────────────────────────────────────────────────────────────┤"
+echo -e "│ ${GREEN}cd /home/ubuntu/Gthulhu/api/web/static && python3 server.py${NC} │"
+echo "│ Purpose: Serves frontend & proxies API requests │"
+echo "└─────────────────────────────────────────────────────────────────────────────┘"
+echo ""
+echo "┌─────────────────────────────────────────────────────────────────────────────┐"
+echo "│ Access the Web UI at: http://localhost:3000 │"
+echo "└─────────────────────────────────────────────────────────────────────────────┘"
+echo ""
+
+# Optionally copy commands to clipboard if xclip is available
+if command -v xclip &> /dev/null; then
+ echo "microk8s kubectl port-forward svc/gthulhu-manager 8080:8080 --address 0.0.0.0" | xclip -selection clipboard
+ echo -e "${YELLOW}📋 Terminal 1 command copied to clipboard${NC}"
+fi
diff --git a/web/static/style.css b/web/static/style.css
index 33bb07b..2b786d3 100644
--- a/web/static/style.css
+++ b/web/static/style.css
@@ -1,567 +1,1909 @@
-* {
+/* ============================================
+ GTHULHU - eBPF Scheduler Control Interface
+ Dark Cyberpunk Theme
+ ============================================ */
+
+/* CSS Custom Properties */
+:root {
+ /* Core Colors */
+ --bg-primary: #0a0e14;
+ --bg-secondary: #0d1117;
+ --bg-tertiary: #161b22;
+ --bg-card: #12171e;
+ --bg-elevated: #1c2128;
+
+ /* Accent Colors */
+ --accent-primary: #00ff88;
+ --accent-secondary: #00d4ff;
+ --accent-tertiary: #7c3aed;
+ --accent-warning: #ffb800;
+ --accent-danger: #ff4757;
+ --accent-success: #00ff88;
+
+ /* Text Colors */
+ --text-primary: #f0f6fc;
+ --text-secondary: #c9d1d9;
+ --text-muted: #6e7681;
+ --text-accent: #00ff88;
+
+ /* Gradients */
+ --gradient-primary: linear-gradient(135deg, #00ff88 0%, #00d4ff 100%);
+ --gradient-accent: linear-gradient(135deg, #7c3aed 0%, #00d4ff 100%);
+ --gradient-dark: linear-gradient(180deg, #0a0e14 0%, #161b22 100%);
+
+ /* Borders */
+ --border-color: #21262d;
+ --border-glow: rgba(0, 255, 136, 0.3);
+
+ /* Shadows */
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
+ --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
+ --shadow-glow: 0 0 20px rgba(0, 255, 136, 0.2);
+
+ /* Typography */
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
+ --font-display: 'Orbitron', sans-serif;
+
+ /* Spacing */
+ --space-xs: 0.25rem;
+ --space-sm: 0.5rem;
+ --space-md: 1rem;
+ --space-lg: 1.5rem;
+ --space-xl: 2rem;
+ --space-2xl: 3rem;
+
+ /* Border Radius */
+ --radius-sm: 4px;
+ --radius-md: 8px;
+ --radius-lg: 12px;
+ --radius-xl: 16px;
+
+ /* Transitions */
+ --transition-fast: 150ms ease;
+ --transition-base: 250ms ease;
+ --transition-slow: 400ms ease;
+}
+
+/* Reset & Base */
+*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
+html {
+ font-size: 16px;
+ scroll-behavior: smooth;
+}
+
body {
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ font-family: var(--font-mono);
+ background: var(--bg-primary);
+ color: var(--text-primary);
line-height: 1.6;
- color: #333;
- background-color: #f5f5f5;
+ min-height: 100vh;
+ overflow-x: hidden;
+}
+
+/* Ambient Background Effects */
+.ambient-grid {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ linear-gradient(rgba(0, 255, 136, 0.03) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 255, 136, 0.03) 1px, transparent 1px);
+ background-size: 50px 50px;
+ pointer-events: none;
+ z-index: 0;
+}
+
+.scan-line {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 2px;
+ background: var(--gradient-primary);
+ opacity: 0.5;
+ animation: scan 8s linear infinite;
+ pointer-events: none;
+ z-index: 1;
+}
+
+@keyframes scan {
+ 0% { transform: translateY(0); opacity: 0; }
+ 10% { opacity: 0.5; }
+ 90% { opacity: 0.5; }
+ 100% { transform: translateY(100vh); opacity: 0; }
}
-header {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- padding: 1rem 2rem;
+/* Header */
+.main-header {
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ background: rgba(10, 14, 20, 0.95);
+ backdrop-filter: blur(20px);
+ border-bottom: 1px solid var(--border-color);
+ padding: var(--space-md) var(--space-xl);
+}
+
+.header-content {
+ max-width: 1400px;
+ margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
-header h1 {
- font-size: 1.8rem;
- font-weight: 300;
+.logo-section {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
}
-.auth-status {
+.logo-icon {
+ width: 52px;
+ height: 52px;
display: flex;
align-items: center;
- gap: 1rem;
+ justify-content: center;
}
-#authText {
- font-size: 0.9rem;
- opacity: 0.9;
+.logo-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ border-radius: var(--radius-md);
+ filter: drop-shadow(0 0 8px rgba(0, 255, 136, 0.4));
+ transition: filter var(--transition-base);
}
-#authBtn {
- background: rgba(255,255,255,0.2);
- border: 1px solid rgba(255,255,255,0.3);
- color: white;
- padding: 0.5rem 1rem;
- border-radius: 4px;
- cursor: pointer;
- transition: background 0.3s;
+.logo-image:hover {
+ filter: drop-shadow(0 0 16px rgba(0, 255, 136, 0.6));
}
-#authBtn:hover {
- background: rgba(255,255,255,0.3);
+.logo-text h1 {
+ font-family: var(--font-display);
+ font-size: 1.5rem;
+ font-weight: 700;
+ letter-spacing: 0.2em;
+ background: var(--gradient-primary);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
}
-main {
- max-width: 1200px;
- margin: 2rem auto;
- padding: 0 2rem;
+.tagline {
+ font-size: 0.7rem;
+ color: var(--text-secondary);
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
}
-.api-section {
- background: white;
- margin-bottom: 2rem;
- padding: 2rem;
- border-radius: 8px;
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+.auth-section {
+ display: flex;
+ align-items: center;
+ gap: var(--space-lg);
}
-.api-section h2 {
- color: #667eea;
- margin-bottom: 1rem;
- font-size: 1.5rem;
- font-weight: 400;
+.connection-status {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ background: var(--bg-tertiary);
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border-color);
}
-.api-section h3 {
- color: #555;
- margin: 1.5rem 0 1rem 0;
- font-size: 1.2rem;
+.status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--accent-danger);
+ box-shadow: 0 0 8px var(--accent-danger);
+ animation: blink 2s ease-in-out infinite;
}
-.api-section h4 {
- color: #666;
- margin: 1rem 0 0.5rem 0;
- font-size: 1rem;
+.connection-status.connected .status-dot {
+ background: var(--accent-success);
+ box-shadow: 0 0 8px var(--accent-success);
+ animation: none;
+}
+
+@keyframes blink {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.4; }
+}
+
+.status-text {
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
}
-.api-btn {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
+.auth-btn {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-lg);
+ background: var(--gradient-primary);
border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 4px;
+ border-radius: var(--radius-md);
+ color: var(--bg-primary);
+ font-family: var(--font-mono);
+ font-weight: 600;
+ font-size: 0.85rem;
cursor: pointer;
- font-size: 1rem;
- transition: transform 0.2s, box-shadow 0.2s;
- margin-bottom: 1rem;
+ transition: var(--transition-base);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
}
-.api-btn:hover {
+.auth-btn:hover {
transform: translateY(-2px);
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
+ box-shadow: var(--shadow-glow);
}
-.api-btn:disabled {
- background: #ccc;
- cursor: not-allowed;
- transform: none;
- box-shadow: none;
+.auth-btn.logout {
+ background: transparent;
+ border: 1px solid var(--accent-danger);
+ color: var(--accent-danger);
}
-.result {
- background: #f8f9fa;
- border: 1px solid #e9ecef;
- border-radius: 4px;
- padding: 1rem;
- margin-top: 1rem;
- font-family: 'Courier New', monospace;
- font-size: 0.9rem;
- white-space: pre-wrap;
- max-height: 400px;
- overflow-y: auto;
- color: #495057;
+.auth-btn.logout:hover {
+ background: rgba(255, 71, 87, 0.1);
}
-.modal {
- display: none;
+/* Modal Styles */
+.modal-overlay {
position: fixed;
- z-index: 1000;
- left: 0;
top: 0;
+ left: 0;
width: 100%;
height: 100%;
- background-color: rgba(0,0,0,0.5);
+ background: rgba(0, 0, 0, 0.8);
+ backdrop-filter: blur(8px);
+ display: none;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+ opacity: 0;
+ transition: opacity var(--transition-base);
}
-.modal-content {
- background-color: white;
- margin: 10% auto;
- padding: 2rem;
- border-radius: 8px;
- width: 80%;
- max-width: 500px;
- position: relative;
- box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+.modal-overlay.active {
+ display: flex;
+ opacity: 1;
}
-.close {
- color: #aaa;
- float: right;
- font-size: 28px;
- font-weight: bold;
- cursor: pointer;
- position: absolute;
- right: 15px;
- top: 10px;
+.modal-container {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-xl);
+ width: 90%;
+ max-width: 480px;
+ box-shadow: var(--shadow-lg), 0 0 60px rgba(0, 255, 136, 0.1);
+ animation: modalSlideIn 0.3s ease;
}
-.close:hover {
- color: #000;
+.modal-container.modal-sm {
+ max-width: 380px;
}
-form {
- display: flex;
- flex-direction: column;
- gap: 1rem;
+@keyframes modalSlideIn {
+ from {
+ transform: translateY(-20px) scale(0.95);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0) scale(1);
+ opacity: 1;
+ }
}
-label {
- font-weight: 500;
- color: #555;
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--space-lg);
+ border-bottom: 1px solid var(--border-color);
}
-input, textarea, select {
- padding: 0.75rem;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 1rem;
- transition: border-color 0.3s;
+.modal-header h2 {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ font-family: var(--font-display);
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: var(--text-primary);
}
-input:focus, textarea:focus, select:focus {
- outline: none;
- border-color: #667eea;
- box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
+.modal-icon {
+ font-size: 1.2rem;
}
-textarea {
- min-height: 150px;
- resize: vertical;
- font-family: 'Courier New', monospace;
+.modal-close {
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: transparent;
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-sm);
+ color: var(--text-secondary);
+ font-size: 1.2rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
}
-button[type="submit"] {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- border: none;
- padding: 0.75rem;
- border-radius: 4px;
- cursor: pointer;
- font-size: 1rem;
- transition: transform 0.2s;
+.modal-close:hover {
+ background: var(--bg-tertiary);
+ color: var(--accent-danger);
+ border-color: var(--accent-danger);
}
-button[type="submit"]:hover {
- transform: translateY(-2px);
+.modal-body {
+ padding: var(--space-lg);
}
-button[type="submit"]:disabled {
- background: #ccc;
- cursor: not-allowed;
- transform: none;
+.modal-footer {
+ padding: var(--space-md) var(--space-lg);
+ border-top: 1px solid var(--border-color);
+ text-align: center;
}
-/* Strategy management styles */
-.strategies-manager {
- margin-top: 1rem;
+.security-note {
+ font-size: 0.75rem;
+ color: var(--text-muted);
}
-.strategy-item {
- border: 1px solid #e9ecef;
- padding: 1.5rem;
- margin-bottom: 1rem;
- border-radius: 8px;
- background: #f8f9fa;
+/* Form Styles */
+.input-group {
position: relative;
+ margin-bottom: var(--space-lg);
}
-.strategy-item h4 {
- color: #667eea;
- margin-bottom: 1rem;
+.input-group label {
display: flex;
- justify-content: space-between;
align-items: center;
+ gap: var(--space-xs);
+ font-size: 0.8rem;
+ font-weight: 500;
+ color: var(--text-secondary);
+ margin-bottom: var(--space-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
}
-.strategy-item .remove-strategy-btn {
- background: #dc3545;
- color: white;
- border: none;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- cursor: pointer;
- font-size: 0.8rem;
+.label-icon {
+ font-size: 0.9rem;
}
-.strategy-item .remove-strategy-btn:hover {
- background: #c82333;
+.input-group input {
+ width: 100%;
+ padding: var(--space-md);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ color: var(--text-primary);
+ font-family: var(--font-mono);
+ font-size: 0.95rem;
+ transition: var(--transition-fast);
}
-.strategy-form {
- display: grid;
- gap: 1rem;
- grid-template-columns: 1fr 1fr;
+.input-group input::placeholder {
+ color: var(--text-muted);
}
-.strategy-form .full-width {
- grid-column: 1 / -1;
+.input-group input:focus {
+ outline: none;
+ border-color: var(--accent-primary);
+ box-shadow: 0 0 0 3px rgba(0, 255, 136, 0.1);
}
-.strategy-form label {
+.input-hint {
display: block;
- margin-bottom: 0.25rem;
- font-weight: 500;
- color: #555;
- font-size: 0.9rem;
+ margin-top: var(--space-xs);
+ font-size: 0.7rem;
+ color: var(--text-muted);
}
-.strategy-form input, .strategy-form textarea {
- width: 100%;
- padding: 0.5rem;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 0.9rem;
+.input-glow {
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 0;
+ height: 2px;
+ background: var(--gradient-primary);
+ transition: width var(--transition-base);
}
-.strategy-form input[type="checkbox"] {
- width: auto;
- margin-right: 0.5rem;
+.input-group input:focus + .input-glow {
+ width: 100%;
}
-.strategy-controls {
- display: flex;
- gap: 1rem;
- margin-top: 1.5rem;
- flex-wrap: wrap;
+.form-actions {
+ margin-top: var(--space-lg);
}
-.add-strategy-btn {
- background: #28a745;
- color: white;
+.submit-btn {
+ width: 100%;
+ padding: var(--space-md) var(--space-lg);
+ background: var(--gradient-primary);
border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 4px;
+ border-radius: var(--radius-md);
+ color: var(--bg-primary);
+ font-family: var(--font-mono);
+ font-weight: 600;
+ font-size: 0.9rem;
cursor: pointer;
- font-size: 1rem;
- transition: background 0.3s;
-}
-
-.add-strategy-btn:hover {
- background: #218838;
+ transition: var(--transition-base);
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ position: relative;
+ overflow: hidden;
}
-.clear-btn {
- background: #6c757d;
- color: white;
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 4px;
- cursor: pointer;
- font-size: 1rem;
- transition: background 0.3s;
+.submit-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-glow);
}
-.clear-btn:hover {
- background: #5a6268;
+.submit-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none;
}
-.selectors-container {
- grid-column: 1 / -1;
+.submit-btn.loading .btn-text {
+ opacity: 0;
}
-.selectors-list {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
+.submit-btn.loading .btn-loader {
+ display: block;
}
-.add-selector-btn {
- background: #17a2b8;
- color: white;
- border: none;
- padding: 0.5rem 1rem;
- border-radius: 4px;
- cursor: pointer;
+.btn-loader {
+ display: none;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 20px;
+ height: 20px;
+ border: 2px solid transparent;
+ border-top-color: var(--bg-primary);
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ to { transform: translate(-50%, -50%) rotate(360deg); }
+}
+
+.error-message {
+ margin-top: var(--space-md);
+ padding: var(--space-sm) var(--space-md);
+ background: rgba(255, 71, 87, 0.1);
+ border: 1px solid var(--accent-danger);
+ border-radius: var(--radius-sm);
+ color: var(--accent-danger);
font-size: 0.85rem;
- margin-top: 0.5rem;
- width: fit-content;
-}
-
-.add-selector-btn:hover {
- background: #138496;
+ display: none;
}
-@media (max-width: 768px) {
- .strategy-form {
- grid-template-columns: 1fr;
- }
-
- .strategy-controls {
- flex-direction: column;
- }
+.error-message.show {
+ display: block;
}
-.strategy-input {
- border: 1px solid #e9ecef;
- padding: 1rem;
- border-radius: 4px;
- background: #f8f9fa;
+/* Dashboard */
+.dashboard {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: var(--space-xl);
+ position: relative;
+ z-index: 2;
}
-.metrics-grid {
- display: grid;
- grid-template-columns: 1fr 2fr;
- gap: 1rem;
- align-items: center;
+/* Quick Actions */
+.quick-actions {
+ display: flex;
+ gap: var(--space-sm);
+ margin-bottom: var(--space-xl);
+ flex-wrap: wrap;
}
-.selector {
+.action-chip {
display: flex;
- gap: 0.5rem;
- margin-bottom: 0.5rem;
align-items: center;
-}
-
-.selector input {
- flex: 1;
-}
-
-.selector button {
- background: #dc3545;
- color: white;
- border: none;
- padding: 0.5rem 1rem;
- border-radius: 4px;
+ gap: var(--space-xs);
+ padding: var(--space-sm) var(--space-md);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
+ color: var(--text-secondary);
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
cursor: pointer;
- font-size: 0.9rem;
+ transition: var(--transition-fast);
}
-.selector button:hover {
- background: #c82333;
+.action-chip:hover {
+ background: var(--bg-elevated);
+ border-color: var(--accent-primary);
+ color: var(--accent-primary);
}
-button[type="button"] {
- background: #28a745;
- color: white;
- border: none;
- padding: 0.5rem 1rem;
- border-radius: 4px;
- cursor: pointer;
- font-size: 0.9rem;
- margin-top: 0.5rem;
+.action-chip:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
}
-button[type="button"]:hover {
- background: #218838;
+/* Dashboard Grid */
+.dashboard-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--space-lg);
}
-.auth-required:disabled {
- opacity: 0.6;
- cursor: not-allowed;
+@media (max-width: 1024px) {
+ .dashboard-grid {
+ grid-template-columns: 1fr;
+ }
}
-.success {
- color: #28a745;
- background: #d4edda;
- border-color: #c3e6cb;
+.full-width {
+ grid-column: 1 / -1;
}
-.error {
- color: #721c24;
- background: #f8d7da;
- border-color: #f5c6cb;
+/* Card Styles */
+.card {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ transition: var(--transition-base);
}
-.info {
- color: #0c5460;
- background: #d1ecf1;
- border-color: #bee5eb;
+.card:hover {
+ border-color: rgba(0, 255, 136, 0.3);
+ box-shadow: 0 0 30px rgba(0, 255, 136, 0.05);
}
-.metrics-info {
- margin-top: 1rem;
- padding: 1rem;
- background: #e3f2fd;
- border-radius: 4px;
- border-left: 4px solid #667eea;
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--space-md) var(--space-lg);
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--border-color);
}
-.metrics-info h3 {
- margin-bottom: 0.5rem;
- color: #4a5568;
- font-size: 1.1rem;
+.card-title {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
}
-.metrics-info ul {
- list-style-type: none;
- padding-left: 0;
+.card-icon {
+ font-size: 1.2rem;
}
-.metrics-info li {
- padding: 0.25rem 0;
- color: #666;
+.card-title h2 {
+ font-family: var(--font-display);
font-size: 0.9rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--text-primary);
}
-.metrics-info strong {
- color: #4a5568;
- font-weight: 600;
+.card-actions {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
}
-@media (max-width: 768px) {
- header {
- flex-direction: column;
- gap: 1rem;
- text-align: center;
- }
-
- .metrics-grid {
- grid-template-columns: 1fr;
- }
-
- .modal-content {
- width: 95%;
- margin: 5% auto;
- }
-
- .selector {
- flex-direction: column;
- }
+.card-body {
+ padding: var(--space-lg);
}
-/* Control panel styles */
-.control-panel {
+/* Icon Button */
+.icon-btn {
+ width: 36px;
+ height: 36px;
display: flex;
align-items: center;
- gap: 20px;
- margin-bottom: 15px;
- flex-wrap: wrap;
+ justify-content: center;
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-sm);
+ color: var(--text-secondary);
+ font-size: 1rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
}
-.interval-controls {
- display: flex;
- align-items: center;
- gap: 10px;
+.icon-btn:hover:not(:disabled) {
+ background: var(--bg-tertiary);
+ border-color: var(--accent-primary);
+ color: var(--accent-primary);
}
-.interval-controls label {
- margin: 0;
- font-weight: normal;
+.icon-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
}
-.interval-controls input[type="number"] {
- width: 80px;
- padding: 5px;
- border: 1px solid #ddd;
- border-radius: 4px;
+/* Primary Button */
+.primary-btn {
+ display: flex;
+ align-items: center;
+ gap: var(--space-xs);
+ padding: var(--space-sm) var(--space-md);
+ background: var(--gradient-primary);
+ border: none;
+ border-radius: var(--radius-sm);
+ color: var(--bg-primary);
+ font-family: var(--font-mono);
+ font-weight: 600;
+ font-size: 0.8rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
}
-/* Health status grid */
-.health-status-grid {
- margin-bottom: 15px;
+.primary-btn:hover:not(:disabled) {
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-glow);
}
-.status-grid {
- display: flex;
- gap: 5px;
- margin: 10px 0;
- flex-wrap: wrap;
+.primary-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
}
-.status-box {
- width: 30px;
- height: 30px;
- border: 2px solid #ddd;
- border-radius: 4px;
+/* Success/Danger Buttons */
+.success-btn {
display: flex;
align-items: center;
- justify-content: center;
- font-size: 12px;
- font-weight: bold;
- color: white;
- position: relative;
- transition: all 0.3s ease;
+ gap: var(--space-xs);
+ padding: var(--space-sm) var(--space-md);
+ background: var(--accent-success);
+ border: none;
+ border-radius: var(--radius-sm);
+ color: var(--bg-primary);
+ font-family: var(--font-mono);
+ font-weight: 600;
+ font-size: 0.85rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
}
-.status-box.healthy {
- background-color: #4caf50;
- border-color: #388e3c;
+.success-btn:hover:not(:disabled) {
+ transform: translateY(-1px);
+ box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
}
-.status-box.unhealthy {
- background-color: #f44336;
- border-color: #d32f2f;
+.success-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
}
-.status-box.loading {
- background-color: #ff9800;
- border-color: #f57c00;
+.danger-btn {
+ display: flex;
+ align-items: center;
+ gap: var(--space-xs);
+ padding: var(--space-sm) var(--space-md);
+ background: transparent;
+ border: 1px solid var(--accent-danger);
+ border-radius: var(--radius-sm);
+ color: var(--accent-danger);
+ font-family: var(--font-mono);
+ font-weight: 600;
+ font-size: 0.85rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
}
-.grid-legend {
- display: flex;
- gap: 15px;
- font-size: 14px;
- margin-top: 10px;
+.danger-btn:hover {
+ background: rgba(255, 71, 87, 0.1);
}
-.legend-item {
+/* Auto Refresh Toggle */
+.auto-refresh-toggle {
display: flex;
align-items: center;
- gap: 5px;
+ gap: var(--space-xs);
}
-.legend-item .status-box {
- width: 15px;
- height: 15px;
+.auto-refresh-toggle input[type="checkbox"] {
+ appearance: none;
+ width: 36px;
+ height: 20px;
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ cursor: pointer;
+ position: relative;
+ transition: var(--transition-fast);
}
+
+.auto-refresh-toggle input[type="checkbox"]::after {
+ content: '';
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 14px;
+ height: 14px;
+ background: var(--text-muted);
+ border-radius: 50%;
+ transition: var(--transition-fast);
+}
+
+.auto-refresh-toggle input[type="checkbox"]:checked {
+ background: var(--accent-primary);
+ border-color: var(--accent-primary);
+}
+
+.auto-refresh-toggle input[type="checkbox"]:checked::after {
+ transform: translateX(16px);
+ background: var(--bg-primary);
+}
+
+.auto-refresh-toggle label {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ text-transform: uppercase;
+}
+
+/* Health Card Specific */
+.health-display {
+ display: flex;
+ gap: var(--space-xl);
+ align-items: center;
+ margin-bottom: var(--space-lg);
+}
+
+.health-indicator {
+ flex-shrink: 0;
+}
+
+.health-ring {
+ position: relative;
+ width: 100px;
+ height: 100px;
+}
+
+.health-ring svg {
+ transform: rotate(-90deg);
+}
+
+.ring-bg {
+ fill: none;
+ stroke: var(--bg-tertiary);
+ stroke-width: 8;
+}
+
+.ring-progress {
+ fill: none;
+ stroke: var(--text-muted);
+ stroke-width: 8;
+ stroke-linecap: round;
+ stroke-dasharray: 283;
+ stroke-dashoffset: 283;
+ transition: stroke-dashoffset 0.5s ease, stroke 0.3s ease;
+}
+
+.ring-progress.healthy {
+ stroke: var(--accent-success);
+ stroke-dashoffset: 0;
+}
+
+.ring-progress.unhealthy {
+ stroke: var(--accent-danger);
+ stroke-dashoffset: 141;
+}
+
+.health-status {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-family: var(--font-display);
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: var(--text-muted);
+ text-align: center;
+}
+
+.health-status.healthy {
+ color: var(--accent-success);
+}
+
+.health-status.unhealthy {
+ color: var(--accent-danger);
+}
+
+.health-history {
+ flex: 1;
+}
+
+.history-label {
+ display: block;
+ font-size: 0.7rem;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-bottom: var(--space-sm);
+}
+
+.history-grid {
+ display: flex;
+ gap: 4px;
+ flex-wrap: wrap;
+}
+
+.history-dot {
+ width: 24px;
+ height: 24px;
+ border-radius: var(--radius-sm);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.7rem;
+ color: var(--text-muted);
+ transition: var(--transition-fast);
+}
+
+.history-dot.healthy {
+ background: rgba(0, 255, 136, 0.2);
+ border-color: var(--accent-success);
+ color: var(--accent-success);
+}
+
+.history-dot.unhealthy {
+ background: rgba(255, 71, 87, 0.2);
+ border-color: var(--accent-danger);
+ color: var(--accent-danger);
+}
+
+/* Code Block */
+.code-block {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ padding: var(--space-md);
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+ overflow-x: auto;
+ white-space: pre-wrap;
+ word-break: break-word;
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.code-block.success {
+ border-color: var(--accent-success);
+ background: rgba(0, 255, 136, 0.05);
+}
+
+.code-block.error {
+ border-color: var(--accent-danger);
+ background: rgba(255, 71, 87, 0.05);
+}
+
+/* Metrics Grid */
+.metrics-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: var(--space-md);
+ margin-bottom: var(--space-lg);
+}
+
+.metric-item {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ padding: var(--space-md);
+ text-align: center;
+ transition: var(--transition-fast);
+}
+
+.metric-item:hover {
+ border-color: var(--accent-secondary);
+ box-shadow: 0 0 15px rgba(0, 212, 255, 0.1);
+}
+
+.metric-item.accent {
+ border-color: rgba(0, 255, 136, 0.3);
+ background: rgba(0, 255, 136, 0.05);
+}
+
+.metric-value {
+ display: block;
+ font-family: var(--font-display);
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: var(--space-xs);
+}
+
+.metric-label {
+ font-size: 0.7rem;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+/* Strategies Container */
+.strategies-container {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-md);
+ margin-bottom: var(--space-lg);
+}
+
+.strategy-item {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ padding: var(--space-lg);
+ position: relative;
+ animation: slideIn 0.3s ease;
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.strategy-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--space-md);
+ padding-bottom: var(--space-sm);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.strategy-header h4 {
+ font-family: var(--font-display);
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: var(--accent-secondary);
+}
+
+.remove-strategy-btn {
+ padding: var(--space-xs) var(--space-sm);
+ background: transparent;
+ border: 1px solid var(--accent-danger);
+ border-radius: var(--radius-sm);
+ color: var(--accent-danger);
+ font-size: 0.75rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.remove-strategy-btn:hover {
+ background: rgba(255, 71, 87, 0.1);
+}
+
+.strategy-form {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--space-md);
+}
+
+.strategy-form .full-width {
+ grid-column: 1 / -1;
+}
+
+.strategy-form label {
+ display: block;
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ margin-bottom: var(--space-xs);
+ text-transform: uppercase;
+}
+
+.strategy-form input[type="text"],
+.strategy-form input[type="number"] {
+ width: 100%;
+ padding: var(--space-sm);
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-sm);
+ color: var(--text-primary);
+ font-family: var(--font-mono);
+ font-size: 0.85rem;
+}
+
+.strategy-form input:focus {
+ outline: none;
+ border-color: var(--accent-secondary);
+}
+
+.checkbox-group {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+}
+
+.checkbox-group input[type="checkbox"] {
+ appearance: none;
+ width: 18px;
+ height: 18px;
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ position: relative;
+}
+
+.checkbox-group input[type="checkbox"]:checked {
+ background: var(--accent-primary);
+ border-color: var(--accent-primary);
+}
+
+.checkbox-group input[type="checkbox"]:checked::after {
+ content: '✓';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ color: var(--bg-primary);
+ font-size: 0.7rem;
+ font-weight: bold;
+}
+
+.checkbox-group label {
+ margin: 0;
+ cursor: pointer;
+}
+
+/* Selectors */
+.selectors-container {
+ margin-top: var(--space-sm);
+}
+
+.selectors-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.selector-row {
+ display: flex;
+ gap: var(--space-sm);
+ align-items: center;
+}
+
+.selector-row input {
+ flex: 1;
+ padding: var(--space-sm);
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-sm);
+ color: var(--text-primary);
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
+}
+
+.selector-row input:focus {
+ outline: none;
+ border-color: var(--accent-secondary);
+}
+
+.selector-row button {
+ padding: var(--space-xs) var(--space-sm);
+ background: transparent;
+ border: 1px solid var(--accent-danger);
+ border-radius: var(--radius-sm);
+ color: var(--accent-danger);
+ font-size: 0.7rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.selector-row button:hover {
+ background: rgba(255, 71, 87, 0.1);
+}
+
+.add-selector-btn {
+ margin-top: var(--space-sm);
+ padding: var(--space-xs) var(--space-sm);
+ background: transparent;
+ border: 1px dashed var(--border-color);
+ border-radius: var(--radius-sm);
+ color: var(--text-muted);
+ font-size: 0.75rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.add-selector-btn:hover {
+ border-color: var(--accent-secondary);
+ color: var(--accent-secondary);
+}
+
+/* Strategies Actions */
+.strategies-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--space-md);
+ margin-bottom: var(--space-lg);
+}
+
+/* Footer */
+.main-footer {
+ padding: var(--space-lg);
+ text-align: center;
+ border-top: 1px solid var(--border-color);
+ background: var(--bg-secondary);
+}
+
+.footer-content {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: var(--space-md);
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+.separator {
+ color: var(--border-color);
+}
+
+.github-link {
+ color: var(--text-secondary);
+ text-decoration: none;
+ transition: var(--transition-fast);
+}
+
+.github-link:hover {
+ color: var(--accent-primary);
+}
+
+/* Toast Notifications */
+.toast-container {
+ position: fixed;
+ bottom: var(--space-xl);
+ right: var(--space-xl);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+ z-index: 2000;
+}
+
+.toast {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-md) var(--space-lg);
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-lg);
+ animation: toastIn 0.3s ease;
+ min-width: 280px;
+}
+
+@keyframes toastIn {
+ from {
+ opacity: 0;
+ transform: translateX(100%);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.toast.success {
+ border-color: var(--accent-success);
+}
+
+.toast.error {
+ border-color: var(--accent-danger);
+}
+
+.toast.info {
+ border-color: var(--accent-secondary);
+}
+
+.toast-icon {
+ font-size: 1.2rem;
+}
+
+.toast-message {
+ flex: 1;
+ font-size: 0.85rem;
+ color: var(--text-primary);
+}
+
+.toast-close {
+ background: none;
+ border: none;
+ color: var(--text-muted);
+ cursor: pointer;
+ font-size: 1rem;
+ padding: 0;
+}
+
+.toast-close:hover {
+ color: var(--text-primary);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .main-header {
+ padding: var(--space-md);
+ }
+
+ .header-content {
+ flex-direction: column;
+ gap: var(--space-md);
+ }
+
+ .logo-text h1 {
+ font-size: 1.2rem;
+ }
+
+ .auth-section {
+ width: 100%;
+ justify-content: space-between;
+ }
+
+ .dashboard {
+ padding: var(--space-md);
+ }
+
+ .health-display {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .metrics-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .strategy-form {
+ grid-template-columns: 1fr;
+ }
+
+ .strategies-actions {
+ flex-direction: column;
+ }
+
+ .toast-container {
+ left: var(--space-md);
+ right: var(--space-md);
+ bottom: var(--space-md);
+ }
+
+ .toast {
+ min-width: unset;
+ }
+}
+
+/* Scrollbar Styling */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--bg-tertiary);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border-color);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--text-muted);
+}
+
+/* Selection */
+::selection {
+ background: rgba(0, 255, 136, 0.3);
+ color: var(--text-primary);
+}
+
+/* ============================================
+ Pod-PID Mapping Card Styles
+ ============================================ */
+
+.pods-card {
+ background: linear-gradient(145deg, var(--bg-card) 0%, rgba(0, 212, 255, 0.05) 100%);
+ border-color: rgba(0, 212, 255, 0.3);
+}
+
+.pods-summary {
+ display: flex;
+ gap: var(--space-xl);
+ padding: var(--space-lg);
+ background: var(--bg-tertiary);
+ border-radius: var(--radius-md);
+ margin-bottom: var(--space-lg);
+ flex-wrap: wrap;
+}
+
+.summary-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 80px;
+}
+
+.summary-value {
+ font-family: var(--font-display);
+ font-size: 1.75rem;
+ font-weight: 700;
+ color: var(--accent-secondary);
+ text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
+}
+
+.summary-label {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-top: var(--space-xs);
+}
+
+.pods-table-container {
+ overflow-x: auto;
+ margin-bottom: var(--space-lg);
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border-color);
+}
+
+.pods-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.85rem;
+}
+
+.pods-table th,
+.pods-table td {
+ padding: var(--space-sm) var(--space-md);
+ text-align: left;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.pods-table th {
+ background: var(--bg-tertiary);
+ color: var(--accent-secondary);
+ font-weight: 600;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ position: sticky;
+ top: 0;
+ z-index: 1;
+}
+
+.pods-table tbody tr:hover {
+ background: rgba(0, 212, 255, 0.05);
+}
+
+.pods-table td {
+ color: var(--text-secondary);
+ font-family: var(--font-mono);
+}
+
+.pods-table .pid-cell {
+ color: var(--accent-primary);
+ font-weight: 600;
+}
+
+.pods-table .command-cell {
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--accent-warning);
+}
+
+.pods-table .empty-state {
+ text-align: center;
+ color: var(--text-muted);
+ padding: var(--space-xl);
+ font-style: italic;
+}
+
+.pods-table .empty-state.error {
+ color: var(--accent-danger);
+}
+
+.pods-result details {
+ margin-top: var(--space-md);
+}
+
+.pods-result summary {
+ cursor: pointer;
+ color: var(--text-muted);
+ font-size: 0.85rem;
+ padding: var(--space-sm);
+ user-select: none;
+}
+
+.pods-result summary:hover {
+ color: var(--text-secondary);
+}
+
+.pods-result details[open] summary {
+ margin-bottom: var(--space-sm);
+ color: var(--accent-secondary);
+}
+
+/* Responsive adjustments for pods table */
+@media (max-width: 768px) {
+ .pods-summary {
+ gap: var(--space-md);
+ justify-content: space-around;
+ }
+
+ .summary-value {
+ font-size: 1.25rem;
+ }
+
+ .pods-table {
+ font-size: 0.75rem;
+ }
+
+ .pods-table th,
+ .pods-table td {
+ padding: var(--space-xs) var(--space-sm);
+ }
+
+ .pods-table .command-cell {
+ max-width: 100px;
+ }
+}
+/* ============================================
+ User Profile Card - Enhanced Design
+ ============================================ */
+
+.user-profile-display {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: var(--space-lg);
+ padding: var(--space-lg);
+ background: linear-gradient(135deg, rgba(0, 255, 136, 0.03) 0%, rgba(0, 212, 255, 0.03) 100%);
+ border-radius: var(--radius-lg);
+ border: 1px solid rgba(0, 255, 136, 0.1);
+}
+
+.user-avatar {
+ position: relative;
+}
+
+.avatar-circle {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--font-display);
+ font-size: 2rem;
+ font-weight: 700;
+ color: var(--bg-primary);
+ box-shadow: 0 0 30px rgba(0, 255, 136, 0.3);
+ animation: avatarPulse 3s ease-in-out infinite;
+}
+
+@keyframes avatarPulse {
+ 0%, 100% { box-shadow: 0 0 20px rgba(0, 255, 136, 0.3); }
+ 50% { box-shadow: 0 0 40px rgba(0, 255, 136, 0.5); }
+}
+
+.user-status-indicator {
+ position: absolute;
+ bottom: 4px;
+ right: 4px;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--text-muted);
+ border: 3px solid var(--bg-card);
+ transition: background var(--transition-base);
+}
+
+.user-status-indicator.online {
+ background: var(--accent-success);
+ box-shadow: 0 0 10px var(--accent-success);
+}
+
+.user-main-info {
+ text-align: center;
+}
+
+.user-name {
+ font-family: var(--font-display);
+ font-size: 1.4rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: var(--space-xs);
+ text-shadow: 0 0 20px rgba(0, 255, 136, 0.2);
+}
+
+.user-email-display {
+ font-size: 0.85rem;
+ color: var(--text-muted);
+ font-family: var(--font-mono);
+}
+
+.user-meta-grid {
+ display: flex;
+ gap: var(--space-xl);
+ padding-top: var(--space-md);
+ border-top: 1px solid var(--border-color);
+ width: 100%;
+ justify-content: center;
+}
+
+.user-meta-item {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+}
+
+.user-meta-item .meta-icon {
+ font-size: 1.2rem;
+}
+
+.user-meta-item .meta-content {
+ display: flex;
+ flex-direction: column;
+}
+
+.user-meta-item .meta-label {
+ font-size: 0.65rem;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.user-meta-item .meta-value {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: var(--accent-secondary);
+}
+
+.user-raw-details {
+ margin-top: var(--space-md);
+}
+
+.user-raw-details summary {
+ cursor: pointer;
+ font-size: 0.8rem;
+ color: var(--text-muted);
+ padding: var(--space-sm);
+}
+
+.user-raw-details summary:hover {
+ color: var(--text-secondary);
+}
+
+/* ============================================
+ Scheduling Strategies Card - Enhanced
+ ============================================ */
+
+.strategies-card .card-title h2 {
+ font-size: 1.1rem;
+ color: var(--accent-warning);
+ text-shadow: 0 0 10px rgba(255, 184, 0, 0.3);
+}
+
+.strategy-item {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ margin-bottom: var(--space-md);
+ overflow: hidden;
+}
+
+.strategy-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--space-md);
+ background: var(--bg-elevated);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.strategy-header h4 {
+ font-family: var(--font-display);
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--accent-warning);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.strategy-form {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: var(--space-md);
+ padding: var(--space-lg);
+}
+
+.strategy-form label {
+ display: block;
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.03em;
+ margin-bottom: var(--space-xs);
+}
+
+.strategy-form input {
+ width: 100%;
+ padding: var(--space-sm) var(--space-md);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-sm);
+ color: var(--text-primary);
+ font-family: var(--font-mono);
+ font-size: 0.9rem;
+}
+
+.strategy-form input:focus {
+ outline: none;
+ border-color: var(--accent-warning);
+ box-shadow: 0 0 0 3px rgba(255, 184, 0, 0.1);
+}
+
+.remove-strategy-btn {
+ padding: var(--space-xs) var(--space-sm);
+ background: transparent;
+ border: 1px solid var(--accent-danger);
+ border-radius: var(--radius-sm);
+ color: var(--accent-danger);
+ font-size: 0.75rem;
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.remove-strategy-btn:hover {
+ background: rgba(255, 71, 87, 0.1);
+}
+
+/* ============================================
+ Pod-PID Mapping - Accordion Design
+ ============================================ */
+
+.pods-accordion {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.pods-empty-state {
+ text-align: center;
+ color: var(--text-muted);
+ padding: var(--space-xl);
+ font-style: italic;
+}
+
+.pod-accordion-item {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ transition: border-color var(--transition-fast);
+}
+
+.pod-accordion-item:hover {
+ border-color: rgba(0, 212, 255, 0.3);
+}
+
+.pod-accordion-item.expanded {
+ border-color: var(--accent-secondary);
+}
+
+.pod-accordion-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-md) var(--space-lg);
+ background: var(--bg-elevated);
+ cursor: pointer;
+ transition: background var(--transition-fast);
+ user-select: none;
+}
+
+.pod-accordion-header:hover {
+ background: rgba(0, 212, 255, 0.05);
+}
+
+.pod-accordion-info {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ flex: 1;
+ min-width: 0;
+}
+
+.pod-accordion-toggle {
+ font-size: 0.8rem;
+ color: var(--accent-secondary);
+ transition: transform var(--transition-fast);
+ flex-shrink: 0;
+}
+
+.pod-accordion-item.expanded .pod-accordion-toggle {
+ transform: rotate(90deg);
+}
+
+.pod-accordion-title {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-xs);
+ min-width: 0;
+}
+
+.pod-uid-full {
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
+ color: var(--accent-secondary);
+ word-break: break-all;
+ line-height: 1.3;
+}
+
+.pod-accordion-meta {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ flex-shrink: 0;
+}
+
+.process-count-badge {
+ font-size: 0.75rem;
+ padding: var(--space-xs) var(--space-sm);
+ background: rgba(255, 184, 0, 0.15);
+ border: 1px solid rgba(255, 184, 0, 0.3);
+ border-radius: var(--radius-sm);
+ color: var(--accent-warning);
+ font-weight: 600;
+}
+
+.pod-header-left {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ flex: 1;
+ min-width: 0;
+}
+
+.pod-expand-icon {
+ font-size: 0.8rem;
+ color: var(--accent-secondary);
+ transition: transform var(--transition-fast);
+ flex-shrink: 0;
+}
+
+.pod-accordion-item.expanded .pod-expand-icon {
+ transform: rotate(90deg);
+}
+
+.pod-uid-display {
+ font-family: var(--font-mono);
+ font-size: 0.85rem;
+ color: var(--accent-secondary);
+ word-break: break-all;
+}
+
+.pod-id-badge {
+ font-size: 0.75rem;
+ padding: var(--space-xs) var(--space-sm);
+ background: rgba(0, 255, 136, 0.1);
+ border: 1px solid rgba(0, 255, 136, 0.2);
+ border-radius: var(--radius-sm);
+ color: var(--accent-primary);
+ white-space: nowrap;
+}
+
+.pod-process-count {
+ display: flex;
+ align-items: center;
+ gap: var(--space-xs);
+ font-size: 0.8rem;
+ color: var(--text-muted);
+ flex-shrink: 0;
+}
+
+.pod-process-count .count-badge {
+ font-family: var(--font-display);
+ font-weight: 700;
+ color: var(--accent-warning);
+}
+
+.pod-accordion-content {
+ display: none;
+ padding: 0;
+ background: var(--bg-secondary);
+}
+
+.pod-accordion-item.expanded .pod-accordion-content {
+ display: block;
+}
+
+.pod-processes-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.8rem;
+}
+
+.pod-processes-table th {
+ padding: var(--space-sm) var(--space-md);
+ background: var(--bg-tertiary);
+ color: var(--accent-secondary);
+ font-weight: 600;
+ font-size: 0.7rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ text-align: left;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.pod-processes-table td {
+ padding: var(--space-sm) var(--space-md);
+ color: var(--text-secondary);
+ font-family: var(--font-mono);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.03);
+}
+
+.pod-processes-table tr:hover td {
+ background: rgba(0, 212, 255, 0.03);
+}
+
+.pod-processes-table .pid-value {
+ color: var(--accent-primary);
+ font-weight: 600;
+}
+
+.pod-processes-table .command-value {
+ color: var(--accent-warning);
+ max-width: 250px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.pod-processes-table .ppid-value {
+ color: var(--text-muted);
+}
+
+/* Node name in summary */
+#nodeNameDisplay {
+ color: var(--accent-primary) !important;
+ font-size: 1.2rem !important;
+}
\ No newline at end of file