feat: Implement UVM Dashboard with frontend and backend integration
- 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.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
@@ -39,6 +40,15 @@ class OperationResult:
|
||||
error: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LogTail:
|
||||
content: str
|
||||
truncated: bool
|
||||
|
||||
|
||||
_MAX_LOG_TAIL_BYTES = 262_144
|
||||
|
||||
|
||||
class LifecycleService:
|
||||
"""All mutating entry points use operation lock -> short state transaction."""
|
||||
|
||||
@@ -415,6 +425,41 @@ class LifecycleService:
|
||||
self._validate_paths(vm)
|
||||
return Path(vm.log)
|
||||
|
||||
def read_log_tail(self, identifier: str, *, tail: int) -> LogTail:
|
||||
"""Read a bounded tail from a validated VM log without following it."""
|
||||
if type(tail) is not int or tail < 0:
|
||||
raise ValidationError("log tail must be a non-negative integer")
|
||||
path = self.log_path(identifier)
|
||||
try:
|
||||
with path.open("rb") as stream:
|
||||
end = stream.seek(0, os.SEEK_END)
|
||||
if tail == 0:
|
||||
return LogTail(content="", truncated=False)
|
||||
start = max(0, end - _MAX_LOG_TAIL_BYTES)
|
||||
previous = b""
|
||||
if start:
|
||||
stream.seek(start - 1)
|
||||
previous = stream.read(1)
|
||||
stream.seek(start)
|
||||
content = stream.read(end - start)
|
||||
except OSError as error:
|
||||
raise UvmError("could not read VM log") from error
|
||||
|
||||
truncated = False
|
||||
if start and previous != b"\n":
|
||||
newline = content.find(b"\n")
|
||||
if newline == -1:
|
||||
truncated = True
|
||||
else:
|
||||
content = content[newline + 1:]
|
||||
lines = content.splitlines(keepends=True)
|
||||
if start and len(lines) < tail:
|
||||
truncated = True
|
||||
return LogTail(
|
||||
content=b"".join(lines[-tail:]).decode("utf-8", errors="replace"),
|
||||
truncated=truncated,
|
||||
)
|
||||
|
||||
def diagnostics(self) -> list[dict]:
|
||||
checks = [("root", require_root), ("kvm", check_kvm),
|
||||
("Firecracker", self._verify_firecracker),
|
||||
|
||||
Reference in New Issue
Block a user