Show durable connection requests and timestamped node availability
This commit is contained in:
@@ -84,7 +84,7 @@
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-x-3 gap-y-1 items-center justify-between text-xs text-white/40 mt-1">
|
||||
<span>{{ node.uptime_secs === null ? 'Uptime unavailable' : 'Up ' + formatUptime(node.uptime_secs) }}</span>
|
||||
<span>{{ fleetStatus(node.reported_at) === 'unknown' ? 'Status unknown' : fleetStatus(node.reported_at) === 'offline' ? 'Offline · last seen ' + timeAgo(node.reported_at) : 'Seen ' + timeAgo(node.reported_at) }}</span>
|
||||
<span>{{ fleetStatus(node.reported_at) === 'unknown' ? 'Status unknown' : fleetStatus(node.reported_at) === 'stale' ? 'Not reporting · last seen ' + timeAgo(node.reported_at) : 'Seen ' + timeAgo(node.reported_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<p class="text-2xl font-bold text-white">{{ nodeCount }}</p>
|
||||
<p class="text-xs text-white/40">
|
||||
<span class="fleet-dot-online"></span> {{ onlineCount }} online
|
||||
<span class="ml-1 fleet-dot-offline"></span> {{ offlineCount }} offline
|
||||
<span class="ml-1 fleet-dot-offline"></span> {{ offlineCount }} not reporting
|
||||
<span v-if="unknownCount" class="ml-1">{{ unknownCount }} unknown</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('fleet unavailable readings', () => {
|
||||
expect(cards).toHaveLength(2)
|
||||
expect(cards[0]!.text()).toContain('0%')
|
||||
expect(cards[0]!.text()).toContain('—')
|
||||
expect(cards[0]!.text()).toContain('Offline · last seen 2d ago')
|
||||
expect(cards[0]!.text()).toContain('Not reporting · last seen 2d ago')
|
||||
expect(cards[1]!.text()).toContain('Status unknown')
|
||||
expect(cards[1]!.text()).not.toContain('0%')
|
||||
expect(cards[1]!.text()).toContain('Uptime unavailable')
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('fleet data helpers', () => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('sorts status with online nodes first, then latest report', () => {
|
||||
it('sorts status with online nodes first without shuffling within a status group', () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date('2026-06-10T12:00:00Z'))
|
||||
const nodes = [
|
||||
@@ -56,8 +56,8 @@ describe('fleet data helpers', () => {
|
||||
]
|
||||
|
||||
expect(sortFleetNodes(nodes, 'status').map(n => n.node_id)).toEqual([
|
||||
'online-new',
|
||||
'online-old',
|
||||
'online-new',
|
||||
'offline',
|
||||
])
|
||||
|
||||
@@ -133,9 +133,9 @@ describe('fleet missing data and clock boundaries', () => {
|
||||
expect(isOnline(value)).toBe(false)
|
||||
expect(timeAgo(value)).toBe('Unknown')
|
||||
}
|
||||
expect(fleetStatus('2026-06-10T11:30:00Z')).toBe('offline')
|
||||
expect(fleetStatus('2026-06-10T11:30:00Z')).toBe('stale')
|
||||
expect(fleetStatus('2026-06-10T12:00:30Z')).toBe('online')
|
||||
expect(sortFleetNodes([node('unknown', ''), node('future', '2027-01-01T00:00:00Z'), node('old', '2026-06-10T10:00:00Z'), node('new', '2026-06-10T11:59:00Z')], 'status').map(n => n.node_id)).toEqual(['new', 'old', 'unknown', 'future'])
|
||||
expect(sortFleetNodes([node('unknown', ''), node('future', '2027-01-01T00:00:00Z'), node('old', '2026-06-10T10:00:00Z'), node('new', '2026-06-10T11:59:00Z')], 'status').map(n => n.node_id)).toEqual(['new', 'unknown', 'future', 'old'])
|
||||
})
|
||||
it('keeps valid zero separate from unavailable and invalid measurements', () => {
|
||||
expect(normalizeFleetNode({cpu_pct: 0}).cpu_pct).toBe(0)
|
||||
|
||||
@@ -70,10 +70,10 @@ export function timeAgo(dateStr: string): string {
|
||||
return `${diffDays}d ago`
|
||||
}
|
||||
|
||||
export function fleetStatus(reportedAt: string): 'online' | 'offline' | 'unknown' {
|
||||
export function fleetStatus(reportedAt: string): 'online' | 'stale' | 'unknown' {
|
||||
const age = Date.now() - new Date(reportedAt).getTime()
|
||||
if (!Number.isFinite(age) || age < -60_000) return 'unknown'
|
||||
return age < 30 * 60 * 1000 ? 'online' : 'offline'
|
||||
return age < 30 * 60 * 1000 ? 'online' : 'stale'
|
||||
}
|
||||
|
||||
export function isOnline(reportedAt: string): boolean {
|
||||
@@ -159,10 +159,8 @@ export function sortFleetNodes(nodes: FleetNode[], sortBy: SortOption): FleetNod
|
||||
switch (sortBy) {
|
||||
case 'status':
|
||||
sorted.sort((a, b) => {
|
||||
const aOnline = isOnline(a.reported_at)
|
||||
const bOnline = isOnline(b.reported_at)
|
||||
if (aOnline !== bOnline) return aOnline ? -1 : 1
|
||||
return reportTime(b) - reportTime(a)
|
||||
const rank = { online: 0, unknown: 1, stale: 2 }
|
||||
return rank[fleetStatus(a.reported_at)] - rank[fleetStatus(b.reported_at)]
|
||||
})
|
||||
break
|
||||
case 'last-seen':
|
||||
@@ -277,7 +275,7 @@ export function useFleetData() {
|
||||
// --- Computed ---
|
||||
|
||||
const onlineCount = computed(() => nodes.value.filter(n => isOnline(n.reported_at)).length)
|
||||
const offlineCount = computed(() => nodes.value.filter(n => fleetStatus(n.reported_at) === 'offline').length)
|
||||
const offlineCount = computed(() => nodes.value.filter(n => fleetStatus(n.reported_at) === 'stale').length)
|
||||
const unknownCount = computed(() => nodes.value.filter(n => fleetStatus(n.reported_at) === 'unknown').length)
|
||||
const healthyCount = computed(() => nodes.value.filter(n => isOnline(n.reported_at) && n.cpu_pct !== null && n.mem_pct !== null && n.disk_pct !== null && n.recent_alerts.length === 0).length)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user