- Added dashboard.js for frontend functionality, including VM management and system health monitoring. - Created dashboard route to serve the dashboard HTML. - Introduced diagnostics route for token-protected host diagnostics. - Enhanced VMs router with bulk actions, VM log retrieval, and port management. - Updated lifecycle service to read VM log tails with truncation handling. - Integrated static file serving for dashboard assets in the FastAPI server. - Redirect root URL to the dashboard for improved user experience.
380 lines
19 KiB
HTML
380 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>UVM Dashboard</title>
|
|
<link rel="stylesheet" href="/dashboard/static/css/dashboard.css" />
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<!-- Auth Screen -->
|
|
<div id="auth-screen" class="screen active">
|
|
<div class="auth-card">
|
|
<div class="auth-header">
|
|
<div class="logo">
|
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
<rect width="32" height="32" rx="8" fill="currentColor" />
|
|
<path d="M9 11h14M9 16h10M9 21h6" stroke="#0b0b0f" stroke-width="2" stroke-linecap="round"/>
|
|
</svg>
|
|
<span>UVM</span>
|
|
</div>
|
|
<h1>Welcome back</h1>
|
|
<p>Enter your management API token to access the dashboard.</p>
|
|
</div>
|
|
<div class="auth-form">
|
|
<div class="field">
|
|
<label for="token-input">API Token</label>
|
|
<input id="token-input" type="password" placeholder="Paste your UVM_API_TOKEN here" autocomplete="off" />
|
|
</div>
|
|
<button id="auth-btn" class="btn btn-primary btn-lg">Connect</button>
|
|
<p id="auth-error" class="error-text"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main App -->
|
|
<div id="main-app" class="screen">
|
|
<aside class="sidebar">
|
|
<div class="sidebar-brand">
|
|
<div class="logo">
|
|
<svg width="28" height="28" viewBox="0 0 32 32" fill="none">
|
|
<rect width="32" height="32" rx="8" fill="currentColor" />
|
|
<path d="M9 11h14M9 16h10M9 21h6" stroke="#0b0b0f" stroke-width="2" stroke-linecap="round"/>
|
|
</svg>
|
|
<span>UVM</span>
|
|
</div>
|
|
</div>
|
|
<nav class="sidebar-nav">
|
|
<a href="#overview" class="nav-item active" data-page="overview">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
|
<span>Overview</span>
|
|
</a>
|
|
<a href="#vms" class="nav-item" data-page="vms">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
|
|
<span>Virtual Machines</span>
|
|
</a>
|
|
<a href="#system" class="nav-item" data-page="system">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
<span>System</span>
|
|
</a>
|
|
</nav>
|
|
<div class="sidebar-footer">
|
|
<div class="connection-status">
|
|
<span class="status-dot" id="conn-dot"></span>
|
|
<span class="status-label" id="conn-label">Connected</span>
|
|
</div>
|
|
<button id="logout-btn" class="btn btn-ghost btn-sm">Disconnect</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="main">
|
|
<header class="topbar">
|
|
<div class="topbar-left">
|
|
<h2 id="page-title">Overview</h2>
|
|
</div>
|
|
<div class="topbar-right">
|
|
<button id="refresh-btn" class="btn btn-ghost btn-icon" title="Refresh">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0118.8-4.3M22 12.5a10 10 0 01-18.8 4.3"/></svg>
|
|
</button>
|
|
<button id="create-vm-btn" class="btn btn-primary">+ New VM</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="content">
|
|
<!-- Overview Page -->
|
|
<section id="page-overview" class="page active">
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<span class="stat-label">Total VMs</span>
|
|
<div class="stat-icon icon-blue">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
|
|
</div>
|
|
</div>
|
|
<div class="stat-value" id="stat-total">—</div>
|
|
<div class="stat-meta">registered VMs</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<span class="stat-label">Running</span>
|
|
<div class="stat-icon icon-green">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
|
</div>
|
|
</div>
|
|
<div class="stat-value text-green" id="stat-running">—</div>
|
|
<div class="stat-meta">active microVMs</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<span class="stat-label">Stopped</span>
|
|
<div class="stat-icon icon-amber">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
|
|
</div>
|
|
</div>
|
|
<div class="stat-value text-amber" id="stat-stopped">—</div>
|
|
<div class="stat-meta">inactive microVMs</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<span class="stat-label">API Health</span>
|
|
<div class="stat-icon icon-purple">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
|
|
</div>
|
|
</div>
|
|
<div class="stat-value" id="stat-health">—</div>
|
|
<div class="stat-meta">last check <span id="health-ago">—</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-header">
|
|
<h3>Recent Virtual Machines</h3>
|
|
<a href="#vms" class="link">View all</a>
|
|
</div>
|
|
<div class="vm-grid" id="overview-vm-list">
|
|
<div class="empty-state">
|
|
<p>No virtual machines yet.</p>
|
|
<button class="btn btn-primary btn-sm" onclick="location.hash='#create'">Create your first VM</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- VMs Page -->
|
|
<section id="page-vms" class="page">
|
|
<div class="card">
|
|
<div class="card-toolbar">
|
|
<div class="search-box">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
<input id="vm-search" type="text" placeholder="Search by name, ID, or IP..." />
|
|
</div>
|
|
<div class="toolbar-actions">
|
|
<div id="bulk-controls" class="bulk-controls">
|
|
<span id="bulk-selection-count">0 selected</span>
|
|
<select id="bulk-action" class="select">
|
|
<option value="start">Start</option>
|
|
<option value="restart">Restart</option>
|
|
<option value="stop">Stop</option>
|
|
<option value="ports-sync">Sync ports</option>
|
|
<option value="delete">Delete</option>
|
|
</select>
|
|
<button id="bulk-run-btn" class="btn btn-ghost btn-sm">Run</button>
|
|
</div>
|
|
<select id="vm-filter-status" class="select">
|
|
<option value="all">All statuses</option>
|
|
<option value="running">Running</option>
|
|
<option value="stopped">Stopped</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table class="data-table" id="vms-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="selection"><input id="vm-select-all" type="checkbox" aria-label="Select all visible VMs" /></th>
|
|
<th>VM</th>
|
|
<th>Status</th>
|
|
<th>Guest IP</th>
|
|
<th>Resources</th>
|
|
<th>Ports</th>
|
|
<th>Policy</th>
|
|
<th class="actions">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="table-empty" id="vms-empty">
|
|
<div class="empty-state">
|
|
<p>No virtual machines found.</p>
|
|
<button class="btn btn-primary btn-sm" onclick="location.hash='#create'">Create VM</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- VM Detail Page -->
|
|
<section id="page-vm-detail" class="page">
|
|
<div class="vm-detail-header">
|
|
<button class="btn btn-ghost btn-sm back-btn" onclick="history.back()">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
|
|
Back
|
|
</button>
|
|
<div class="vm-detail-title">
|
|
<h2 id="detail-name">—</h2>
|
|
<span class="badge" id="detail-status">—</span>
|
|
</div>
|
|
</div>
|
|
<div class="detail-grid">
|
|
<div class="card detail-card">
|
|
<h4 class="card-title">Configuration</h4>
|
|
<div class="detail-rows" id="detail-config">
|
|
</div>
|
|
</div>
|
|
<div class="card detail-card">
|
|
<h4 class="card-title">Networking</h4>
|
|
<div class="detail-rows" id="detail-network">
|
|
</div>
|
|
</div>
|
|
<div class="card detail-card">
|
|
<h4 class="card-title">SSH Access</h4>
|
|
<code id="detail-ssh-command" class="ssh-command">ssh root@10.42.0.2</code>
|
|
<button id="detail-ssh-copy-btn" class="btn btn-ghost btn-sm">Copy Command</button>
|
|
<p id="detail-ssh-hint" class="hint">Run this from a trusted local terminal.</p>
|
|
</div>
|
|
<div class="card detail-card wide">
|
|
<h4 class="card-title">Port Rules</h4>
|
|
<div class="port-list" id="detail-ports">
|
|
<p class="muted">No port rules configured.</p>
|
|
</div>
|
|
<div id="detail-ports-edit" class="port-editor">
|
|
<input id="detail-ports-input" class="port-editor-input" type="text" placeholder="e.g. 22:TCP,80:HTTP,443:HTTPS" />
|
|
<div class="port-editor-actions">
|
|
<button id="detail-ports-set" class="btn btn-primary btn-sm">Set</button>
|
|
<button id="detail-ports-open" class="btn btn-ghost btn-sm">Open</button>
|
|
<button id="detail-ports-close" class="btn btn-ghost btn-sm">Close</button>
|
|
<button id="detail-ports-cancel" class="btn btn-ghost btn-sm">Cancel</button>
|
|
</div>
|
|
</div>
|
|
<div class="port-actions">
|
|
<button id="detail-ports-edit-btn" class="btn btn-ghost btn-sm">Edit Ports</button>
|
|
<button id="detail-ports-sync-btn" class="btn btn-ghost btn-sm">Sync Ports</button>
|
|
</div>
|
|
</div>
|
|
<div class="card detail-card wide">
|
|
<div class="card-title-row">
|
|
<h4 class="card-title">Firecracker Log</h4>
|
|
<button id="detail-log-refresh-btn" class="btn btn-ghost btn-sm">Refresh Log</button>
|
|
</div>
|
|
<pre id="detail-log-output" class="log-output">Loading recent log entries…</pre>
|
|
<p id="detail-log-meta" class="hint">Showing up to 100 recent lines.</p>
|
|
</div>
|
|
<div class="card detail-card wide">
|
|
<h4 class="card-title">Actions</h4>
|
|
<div class="action-bar">
|
|
<button id="detail-start-btn" class="btn btn-primary">Start VM</button>
|
|
<button id="detail-restart-btn" class="btn btn-warning">Restart VM</button>
|
|
<button id="detail-stop-btn" class="btn btn-warning">Stop VM</button>
|
|
<button id="detail-delete-btn" class="btn btn-danger">Delete VM</button>
|
|
</div>
|
|
<p class="hint">Use the copied SSH command from a trusted local terminal.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Create VM Page -->
|
|
<section id="page-create" class="page">
|
|
<div class="card create-card">
|
|
<h3 class="card-heading">Create Virtual Machine</h3>
|
|
<div class="form-grid">
|
|
<div class="field">
|
|
<label for="create-name">Name</label>
|
|
<input id="create-name" type="text" placeholder="e.g. web-server" />
|
|
</div>
|
|
<div class="field">
|
|
<label for="create-cpu">vCPUs</label>
|
|
<input id="create-cpu" type="number" min="1" value="1" />
|
|
</div>
|
|
<div class="field">
|
|
<label for="create-ram">RAM</label>
|
|
<input id="create-ram" type="text" placeholder="e.g. 512M or 1G" value="512M" />
|
|
</div>
|
|
<div class="field">
|
|
<label for="create-guest-ip">Guest IP <span class="hint-inline">(optional)</span></label>
|
|
<input id="create-guest-ip" type="text" placeholder="10.42.0.10" />
|
|
</div>
|
|
<div class="field">
|
|
<label for="create-username">Username</label>
|
|
<input id="create-username" type="text" placeholder="root" value="root" />
|
|
</div>
|
|
<div class="field">
|
|
<label for="create-password">Password</label>
|
|
<input id="create-password" type="password" placeholder="root" value="root" />
|
|
</div>
|
|
<div class="field full">
|
|
<label for="create-ports">Ports <span class="hint-inline">comma-separated, e.g. 22,80:HTTP,443:HTTPS</span></label>
|
|
<input id="create-ports" type="text" placeholder="22:TCP,80:HTTP,443:HTTPS" value="22:TCP" />
|
|
</div>
|
|
<div class="field full checkbox-field">
|
|
<label class="checkbox">
|
|
<input id="create-no-default-ports" type="checkbox" />
|
|
<span class="checkmark"></span>
|
|
<span class="checkbox-label">Do not add default SSH port (22)</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button id="create-submit" class="btn btn-primary btn-lg">Create VM</button>
|
|
<button id="create-cancel" class="btn btn-ghost">Cancel</button>
|
|
</div>
|
|
<pre id="create-error" class="error-block"></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- System Page -->
|
|
<section id="page-system" class="page">
|
|
<div class="stats-grid two">
|
|
<div class="card system-card">
|
|
<h4 class="card-title">API Health</h4>
|
|
<div class="system-status" id="system-health">
|
|
<span class="status-badge badge-loading">Checking…</span>
|
|
<p class="muted">Send a test request to the management API.</p>
|
|
</div>
|
|
</div>
|
|
<div class="card system-card">
|
|
<h4 class="card-title">Installation</h4>
|
|
<p class="muted">Install or refresh host assets (Firecracker, kernel, rootfs). This is a privileged operation.</p>
|
|
<div class="system-actions">
|
|
<button id="install-btn" class="btn btn-primary">Install / Refresh</button>
|
|
<label class="checkbox inline">
|
|
<input id="install-force" type="checkbox" />
|
|
<span class="checkmark"></span>
|
|
<span class="checkbox-label">Force redownload</span>
|
|
</label>
|
|
</div>
|
|
<pre id="install-result" class="code-block hidden"></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card wide">
|
|
<h4 class="card-title">Server Information</h4>
|
|
<div class="detail-rows" id="server-info">
|
|
<div class="detail-row"><span class="detail-key">Dashboard Version</span><span class="detail-value">1.0.0</span></div>
|
|
<div class="detail-row"><span class="detail-key">API URL</span><span class="detail-value" id="info-api-url">—</span></div>
|
|
<div class="detail-row"><span class="detail-key">Observed At</span><span class="detail-value" id="info-observed">—</span></div>
|
|
</div>
|
|
</div>
|
|
<div class="card wide">
|
|
<div class="card-title-row">
|
|
<h4 class="card-title">Host Diagnostics</h4>
|
|
<button id="diagnostics-refresh-btn" class="btn btn-ghost btn-sm">Run Checks</button>
|
|
</div>
|
|
<div id="diagnostics-list" class="diagnostics-list">
|
|
<p class="muted">Open System to run host readiness checks.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Toast Container -->
|
|
<div id="toast-container"></div>
|
|
|
|
<!-- Confirm Modal -->
|
|
<div id="confirm-modal" class="modal">
|
|
<div class="modal-overlay"></div>
|
|
<div class="modal-card">
|
|
<h3 id="confirm-title">Are you sure?</h3>
|
|
<p id="confirm-body">This action cannot be undone.</p>
|
|
<div class="modal-actions">
|
|
<button id="confirm-cancel" class="btn btn-ghost">Cancel</button>
|
|
<button id="confirm-ok" class="btn btn-danger">Confirm</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/dashboard/static/js/dashboard.js"></script>
|
|
</body>
|
|
</html>
|