-
+
+
Last update
+
+
+
-
+
+
Nodes
+ {c.nodes != null ? plural(c.nodes, 'node', 'nodes') : ''}
+
+
);
} else if (license.status === 'active') {
body = (
@@ -80,7 +90,7 @@ function ClusterHalf({license, cs}: {license: License; cs: ClusterState}) {
if (!body && !ended) return null;
return (
-
Cluster
+ {!c &&
Cluster
}
{ended}
{body}
diff --git a/apps/app/src/styles.css b/apps/app/src/styles.css
index b129e68..f03045c 100644
--- a/apps/app/src/styles.css
+++ b/apps/app/src/styles.css
@@ -227,3 +227,7 @@ form .muted.small { margin-top: 14px; font-size: 13px; }
.key-row .key { margin: 0; overflow-x: auto; }
.license-main p { margin: 0; }
.license-name { margin-bottom: 4px; }
+.cluster-grid { margin: 0; }
+.cluster-grid dt { margin: 0 0 4px; }
+.cluster-grid dd { margin: 0; font-family: var(--font-mono); font-size: 13px; }
+.cluster-grid dd, .cluster-grid dd .cluster-version { color: var(--text-muted); font-weight: 400; font-size: 13px; }
diff --git a/apps/app/src/test/licenses.test.tsx b/apps/app/src/test/licenses.test.tsx
index eeccf68..67ed5e2 100644
--- a/apps/app/src/test/licenses.test.tsx
+++ b/apps/app/src/test/licenses.test.tsx
@@ -225,7 +225,7 @@ describe('/licenses', () => {
expect(await screen.findByText('7c1e04ab')).toHaveClass('mono');
expect(screen.getByText('v1.0.0')).toBeInTheDocument();
expect(screen.getByText('3 nodes')).toBeInTheDocument();
- expect(screen.getAllByText(/^Last seen /).length).toBe(2);
+ expect(screen.getAllByText(/ ago$|^just now$/).length).toBe(2);
expect(screen.queryByText(/Inactive|Stopped|Waiting|check-in/i)).toBeNull();
expect(screen.queryByText(/reach kwatch.dev/)).toBeNull();
});
@@ -236,10 +236,10 @@ describe('/licenses', () => {
const t = Math.floor(Date.now() / 1000);
withClusters([cl({last_seen: t - 20 * 60})]);
renderAt('/licenses');
- const el = await screen.findByText('Last seen 20 min ago');
+ const el = await screen.findByText('20 min ago');
expect(el).toHaveAttribute('title', expect.stringMatching(/^\d{4}-\d\d-\d\d \d\d:\d\d UTC$/));
await act(async () => { await vi.advanceTimersByTimeAsync(60_000); });
- expect(screen.getByText('Last seen 21 min ago')).toBeInTheDocument();
+ expect(screen.getByText('21 min ago')).toBeInTheDocument();
} finally {
vi.useRealTimers();
}
@@ -260,7 +260,7 @@ describe('/licenses', () => {
'GET /account/clusters': {body: {clusters: [cl({last_seen: t - 600})], srv_time: t + 3600}},
});
renderAt('/licenses');
- expect(await screen.findByText('Last seen 1 h ago')).toBeInTheDocument();
+ expect(await screen.findByText('1 h ago')).toBeInTheDocument();
});
it('sorts non-active licenses first, then newest first', async () => {