Redesign host row layout, fix Docker build, add S3 Singapore

- Host row: two-layer layout with name/URL on the left (normal flow)
  and latency/stats on the right (absolute positioned), preventing
  overlap and keeping sparklines aligned
- Docker: install git in build stage and include .git in context so
  vite can resolve commit hash for footer
- Add S3 ap-southeast-1 (Singapore) endpoint for AWS peering comparison
This commit is contained in:
2026-02-26 18:25:03 +07:00
parent de98e74539
commit 05a2ee970c
3 changed files with 19 additions and 17 deletions

View File

@@ -1,6 +1,5 @@
node_modules node_modules
dist dist
.git
.DS_Store .DS_Store
*.log *.log
.claude .claude

View File

@@ -3,6 +3,7 @@ FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e3
WORKDIR /app WORKDIR /app
COPY package.json yarn.lock ./ COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile RUN yarn install --frozen-lockfile
RUN apk add --no-cache git
COPY . . COPY . .
RUN yarn build RUN yarn build

View File

@@ -551,20 +551,22 @@ function hostRowHTML(host, index, showPin = true) {
: `<div class="flex-shrink-0 w-4 h-4"></div>`; : `<div class="flex-shrink-0 w-4 h-4"></div>`;
return ` return `
<div class="host-row bg-gray-800/50 rounded-lg p-2 border border-gray-700/50" data-index="${index}"> <div class="host-row bg-gray-800/50 rounded-lg p-2 border border-gray-700/50" data-index="${index}">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4 min-w-0">
${pinBtn} ${pinBtn}
<div class="w-72 flex-shrink-0"> <div class="w-[480px] flex-shrink relative min-w-0">
<div class="flex items-center gap-2"> <div class="max-w-[55%]">
<div class="w-3 h-3 rounded-full" style="background-color: ${latencyHex(null)}"></div> <div class="flex items-center gap-2">
<span class="font-medium text-white truncate">${host.name}</span> <div class="w-3 h-3 rounded-full flex-shrink-0" style="background-color: ${latencyHex(null)}"></div>
<span class="font-medium text-white truncate">${host.name}</span>
</div>
<a href="${host.url}" target="_blank" rel="noopener" class="text-xs text-gray-500 truncate block">${host.url}</a>
</div> </div>
<a href="${host.url}" target="_blank" rel="noopener" class="text-xs text-gray-500 mt-1 truncate block">${host.url}</a> <div class="absolute top-0 right-0 text-right">
</div> <div class="latency-value text-4xl font-bold tabular-nums" data-host="${index}">
<div class="w-64 flex-shrink-0 min-w-0 text-right"> <span class="text-gray-500">---</span>
<div class="latency-value text-4xl font-bold tabular-nums" data-host="${index}"> </div>
<span class="text-gray-500">---</span> <div class="status-text text-xs text-gray-500 whitespace-nowrap mt-1 pl-4" data-host="${index}">waiting...</div>
</div> </div>
<div class="status-text text-xs text-gray-500 mt-1 whitespace-nowrap" data-host="${index}">waiting...</div>
</div> </div>
<div class="flex-grow sparkline-container rounded overflow-hidden border border-gray-700/30"> <div class="flex-grow sparkline-container rounded overflow-hidden border border-gray-700/30">
<canvas class="sparkline-canvas w-full" data-host="${index}" height="${CONFIG.canvasHeight}"></canvas> <canvas class="sparkline-canvas w-full" data-host="${index}" height="${CONFIG.canvasHeight}"></canvas>
@@ -722,19 +724,19 @@ function updateHostRow(host, index) {
`<span class="text-gray-400">avg </span><span class="${latencyClass(avg, "online")}">${avg}ms</span>` + `<span class="text-gray-400">avg </span><span class="${latencyClass(avg, "online")}">${avg}ms</span>` +
` <span class="text-gray-500">/</span> ` + ` <span class="text-gray-500">/</span> ` +
`<span class="text-gray-400">max </span><span class="${latencyClass(max, "online")}">${max}ms</span>`; `<span class="text-gray-400">max </span><span class="${latencyClass(max, "online")}">${max}ms</span>`;
statusEl.className = "status-text text-xs mt-1 whitespace-nowrap"; statusEl.className = "status-text text-xs whitespace-nowrap text-right";
} else if (host.status === "offline") { } else if (host.status === "offline") {
statusEl.textContent = "unreachable"; statusEl.textContent = "unreachable";
statusEl.className = statusEl.className =
"status-text text-xs text-red-400 mt-1 whitespace-nowrap"; "status-text text-xs text-red-400 whitespace-nowrap text-right";
} else if (host.status === "error") { } else if (host.status === "error") {
statusEl.textContent = "timeout"; statusEl.textContent = "timeout";
statusEl.className = statusEl.className =
"status-text text-xs text-orange-400 mt-1 whitespace-nowrap"; "status-text text-xs text-orange-400 whitespace-nowrap text-right";
} else { } else {
statusEl.textContent = "connecting..."; statusEl.textContent = "connecting...";
statusEl.className = statusEl.className =
"status-text text-xs text-gray-500 mt-1 whitespace-nowrap"; "status-text text-xs text-gray-500 whitespace-nowrap text-right text-right";
} }
SparklineRenderer.draw(canvas, host.history); SparklineRenderer.draw(canvas, host.history);
@@ -1046,7 +1048,7 @@ function greyOutUI(state) {
if (statusEl) { if (statusEl) {
statusEl.textContent = "paused"; statusEl.textContent = "paused";
statusEl.className = statusEl.className =
"status-text text-xs text-gray-500 mt-1 whitespace-nowrap"; "status-text text-xs text-gray-500 whitespace-nowrap text-right";
} }
// Grey out the status dot // Grey out the status dot
const row = document.querySelector(`.host-row[data-index="${i}"]`); const row = document.querySelector(`.host-row[data-index="${i}"]`);