mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-06 20:56:24 +02:00
When --vmtest-web is set, Host.app is launched with --screenshot-port 0
to start a localhost HTTP server that captures the VZVirtualMachineView
display. The Go test harness parses the SCREENSHOT_PORT=<port> line from
stdout, then polls every 2 seconds for JPEG thumbnails and pushes them
over WebSocket to the web dashboard.
Clicking a screenshot thumbnail opens a full-resolution image proxied
through the web UI's /screenshot/{node} endpoint.
Screenshot events are excluded from the EventBus history (they're large
and only the latest matters, stored in NodeStatus.Screenshot).
Updates #13038
Change-Id: I9bc67ddd1cc72948b33c555d4be3d8db06a41f6d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
113 lines
4.1 KiB
HTML
113 lines
4.1 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>VMTest: {{.TestName}}</title>
|
|
<script src="https://unpkg.com/htmx.org@2.0.4"
|
|
integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
|
|
crossorigin="anonymous"></script>
|
|
<script src="https://unpkg.com/htmx-ext-ws@2.0.2"
|
|
integrity="sha384-932iIqjARv+Gy0+r6RTGrfCkCKS5MsF539Iqf6Vt8L4YmbnnWI2DSFoMD90bvXd0"
|
|
crossorigin="anonymous"></script>
|
|
<link rel="stylesheet" href="style.css">
|
|
</head>
|
|
<body hx-ext="ws" ws-connect="ws">
|
|
|
|
<h1>VMTest: {{.TestName}} <span class="test-status test-{{.TestStatus.State}}" id="test-status">{{.TestStatus.State}} ({{formatDuration .TestStatus.Elapsed}})</span></h1>
|
|
|
|
<div class="steps">
|
|
<h2>Progress</h2>
|
|
{{range .Steps}}
|
|
<div class="step step-{{.Status}}" id="step-{{.Index}}">
|
|
<span class="step-icon">{{.Status.Icon}}</span>
|
|
<span class="step-name">{{.Name}}</span>
|
|
<span class="step-time">{{if ne .Status.String "pending"}}{{formatDuration .Elapsed}}{{end}}</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="vm-grid">
|
|
{{range $node := .Nodes}}
|
|
<div class="vm-card" id="vm-{{$node.Name}}">
|
|
<div class="vm-header">
|
|
<span class="vm-name">{{$node.Name}}</span>
|
|
<span class="vm-os">{{$node.OS}}</span>
|
|
</div>
|
|
<div class="vm-status">
|
|
{{range $i, $nic := $node.NICs}}
|
|
<div class="vm-status-line">
|
|
<span class="vm-status-label">DHCP{{if gt (len $node.NICs) 1}} ({{$nic.NetName}}){{end}}:</span>
|
|
<span class="vm-status-value" id="dhcp-{{$node.Name}}-{{$i}}">{{$nic.DHCP}}</span>
|
|
</div>
|
|
{{end}}
|
|
{{if $node.JoinsTailnet}}
|
|
<div class="vm-status-line">
|
|
<span class="vm-status-label">Tailscale:</span>
|
|
<span class="vm-status-value" id="ts-{{$node.Name}}">{{$node.Tailscale}}</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
<div class="screenshot" id="screenshot-{{$node.Name}}">{{if $node.Screenshot}}<a href="/screenshot/{{$node.Name}}" target="_blank"><img src="{{$node.Screenshot}}"></a>{{end}}</div>
|
|
<div class="console" id="console-{{$node.Name}}">{{range $node.Console}}{{ansi .}}
|
|
{{end}}</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="event-log">
|
|
<h2>Events</h2>
|
|
<div class="events" id="events"></div>
|
|
</div>
|
|
|
|
<script>
|
|
// Tick the elapsed time on the test status badge while the test is running.
|
|
(function() {
|
|
var startTime = {{.TestStatus.StartUnixMilli}};
|
|
var el = document.getElementById("test-status");
|
|
var timer = setInterval(function() {
|
|
if (!el || !el.classList.contains("test-Running")) {
|
|
clearInterval(timer);
|
|
return;
|
|
}
|
|
var elapsed = Date.now() - startTime;
|
|
var secs = elapsed / 1000;
|
|
var text;
|
|
if (secs < 1) {
|
|
text = Math.round(elapsed) + "ms";
|
|
} else {
|
|
text = secs.toFixed(1) + "s";
|
|
}
|
|
el.textContent = "Running (" + text + ")";
|
|
}, 100);
|
|
})();
|
|
|
|
// Auto-scroll console divs to bottom unless user has scrolled up.
|
|
// Re-enable auto-scroll when user scrolls back to the bottom.
|
|
(function() {
|
|
var consoles = document.querySelectorAll(".console");
|
|
consoles.forEach(function(el) {
|
|
el._autoScroll = true;
|
|
el.addEventListener("scroll", function() {
|
|
// At bottom if scrollTop + clientHeight >= scrollHeight - small threshold
|
|
var atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 5;
|
|
el._autoScroll = atBottom;
|
|
});
|
|
});
|
|
// Use MutationObserver to detect when content is added to console divs.
|
|
var observer = new MutationObserver(function(mutations) {
|
|
mutations.forEach(function(m) {
|
|
var el = m.target;
|
|
if (el.classList && el.classList.contains("console") && el._autoScroll) {
|
|
el.scrollTop = el.scrollHeight;
|
|
}
|
|
});
|
|
});
|
|
consoles.forEach(function(el) {
|
|
observer.observe(el, { childList: true, characterData: true, subtree: true });
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|