diff --git a/convex/machines.ts b/convex/machines.ts
index 03dc52c..237af94 100644
--- a/convex/machines.ts
+++ b/convex/machines.ts
@@ -10,6 +10,7 @@ import type { MutationCtx } from "./_generated/server"
const DEFAULT_TENANT_ID = "tenant-atlas"
const DEFAULT_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30 // 30 dias
const ALLOWED_MACHINE_PERSONAS = new Set(["collaborator", "manager"])
+const DEFAULT_OFFLINE_THRESHOLD_MS = 10 * 60 * 1000
type NormalizedIdentifiers = {
macs: string[]
@@ -48,6 +49,26 @@ function getTokenTtlMs(): number {
return parsed
}
+function getOfflineThresholdMs(): number {
+ const raw = process.env["MACHINE_OFFLINE_THRESHOLD_MS"]
+ if (!raw) return DEFAULT_OFFLINE_THRESHOLD_MS
+ const parsed = Number(raw)
+ if (!Number.isFinite(parsed) || parsed <= 0) {
+ return DEFAULT_OFFLINE_THRESHOLD_MS
+ }
+ return parsed
+}
+
+function getStaleThresholdMs(offlineMs: number): number {
+ const raw = process.env["MACHINE_STALE_THRESHOLD_MS"]
+ if (!raw) return offlineMs * 12
+ const parsed = Number(raw)
+ if (!Number.isFinite(parsed) || parsed <= offlineMs) {
+ return offlineMs * 12
+ }
+ return parsed
+}
+
function normalizeIdentifiers(macAddresses: string[], serialNumbers: string[]): NormalizedIdentifiers {
const normalizeMac = (value: string) => value.replace(/[^a-f0-9]/gi, "").toLowerCase()
const normalizeSerial = (value: string) => value.trim().toLowerCase()
@@ -681,9 +702,24 @@ export const listByTenant = query({
.collect()
const activeToken = tokens.find((token) => !token.revoked && token.expiresAt > now) ?? null
- const derivedStatus =
- machine.status ??
- (machine.lastHeartbeatAt && now - machine.lastHeartbeatAt <= 5 * 60 * 1000 ? "online" : machine.lastHeartbeatAt ? "offline" : "unknown")
+ const offlineThresholdMs = getOfflineThresholdMs()
+ const staleThresholdMs = getStaleThresholdMs(offlineThresholdMs)
+ const manualStatus = (machine.status ?? "").toLowerCase()
+ let derivedStatus: string
+ if (["maintenance", "blocked"].includes(manualStatus)) {
+ derivedStatus = manualStatus
+ } else if (machine.lastHeartbeatAt) {
+ const age = now - machine.lastHeartbeatAt
+ if (age <= offlineThresholdMs) {
+ derivedStatus = "online"
+ } else if (age <= staleThresholdMs) {
+ derivedStatus = "offline"
+ } else {
+ derivedStatus = "stale"
+ }
+ } else {
+ derivedStatus = machine.status ?? "unknown"
+ }
const metadata = includeMetadata ? (machine.metadata ?? null) : null
diff --git a/src/components/admin/machines/admin-machines-overview.tsx b/src/components/admin/machines/admin-machines-overview.tsx
index c0859d2..c1e9442 100644
--- a/src/components/admin/machines/admin-machines-overview.tsx
+++ b/src/components/admin/machines/admin-machines-overview.tsx
@@ -441,9 +441,23 @@ function useMachinesQuery(tenantId: string): MachinesQueryItem[] {
)
}
+const DEFAULT_OFFLINE_THRESHOLD_MS = 10 * 60 * 1000
+const DEFAULT_STALE_THRESHOLD_MS = DEFAULT_OFFLINE_THRESHOLD_MS * 12
+
+function parseThreshold(raw: string | undefined, fallback: number) {
+ if (!raw) return fallback
+ const parsed = Number(raw)
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback
+ return parsed
+}
+
+const MACHINE_OFFLINE_THRESHOLD_MS = parseThreshold(process.env.NEXT_PUBLIC_MACHINE_OFFLINE_THRESHOLD_MS, DEFAULT_OFFLINE_THRESHOLD_MS)
+const MACHINE_STALE_THRESHOLD_MS = parseThreshold(process.env.NEXT_PUBLIC_MACHINE_STALE_THRESHOLD_MS, DEFAULT_STALE_THRESHOLD_MS)
+
const statusLabels: Record