From 205afbda6faa173b72dd1f9c11e62556046f34f5 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Fri, 17 Jul 2026 12:57:38 -0700 Subject: [PATCH] Thread onlineStatus computed to FE, consolidate columns, add new status badge --- forge/db/views/Device.js | 13 +++-- frontend/src/components/DevicesBrowser.vue | 6 +-- frontend/src/index.css | 14 +++++- .../components/DeviceOnlineStatusCell.vue | 49 +++++++++++++++++++ frontend/src/types/generated.ts | 6 +++ 5 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 frontend/src/pages/device/components/DeviceOnlineStatusCell.vue diff --git a/forge/db/views/Device.js b/forge/db/views/Device.js index c4c7ff7c79..d5c68b4bb4 100644 --- a/forge/db/views/Device.js +++ b/forge/db/views/Device.js @@ -20,6 +20,7 @@ module.exports = function (app) { allOf: [{ $ref: 'SnapshotSummary' }] }, status: { type: 'string' }, + onlineStatus: { type: 'string', enum: ['online', 'offline', 'not-seen'] }, isDeploying: { type: 'boolean' }, agentVersion: { nullable: true, type: 'string' }, mode: { type: 'string' }, @@ -36,7 +37,7 @@ module.exports = function (app) { localLoginEnabled: { type: 'boolean' } }, // POST /devices/ extends this via allOf with credentials + meta. - required: ['id', 'lastSeenAt', 'lastSeenMs', 'status', 'mode', 'isDeploying'] + required: ['id', 'lastSeenAt', 'lastSeenMs', 'status', 'onlineStatus', 'mode', 'isDeploying'] }) function device (device, { statusOnly = false } = {}) { @@ -52,6 +53,7 @@ module.exports = function (app) { lastSeenAt: result.lastSeenAt, lastSeenMs: result.lastSeenAt ? (Date.now() - new Date(result.lastSeenAt).valueOf()) : null, status: result.state || 'offline', + onlineStatus: device.status, mode: result.mode || 'autonomous', isDeploying: app.db.controllers.Device.isDeploying(device) } @@ -69,6 +71,7 @@ module.exports = function (app) { targetSnapshot: app.db.views.ProjectSnapshot.snapshotSummary(device.targetSnapshot), links: result.links, status: result.state || 'offline', + onlineStatus: device.status, agentVersion: result.agentVersion, mode: result.mode || 'autonomous', ownerType: result.ownerType, @@ -106,6 +109,7 @@ module.exports = function (app) { lastSeenAt: { nullable: true, type: 'string' }, lastSeenMs: { nullable: true, type: 'number' }, status: { type: 'string' }, + onlineStatus: { type: 'string', enum: ['online', 'offline', 'not-seen'] }, mode: { type: 'string' }, isDeploying: { type: 'boolean' }, links: { $ref: 'LinksMeta' }, @@ -113,7 +117,7 @@ module.exports = function (app) { mostRecentAuditLogCreatedAt: { type: 'string' }, mostRecentAuditLogEvent: { type: 'string' } }, - required: ['id', 'ownerType', 'name', 'type', 'lastSeenAt', 'lastSeenMs', 'status', 'mode', 'isDeploying', 'links'], + required: ['id', 'ownerType', 'name', 'type', 'lastSeenAt', 'lastSeenMs', 'status', 'onlineStatus', 'mode', 'isDeploying', 'links'], additionalProperties: false }) app.addSchema({ @@ -134,6 +138,7 @@ module.exports = function (app) { lastSeenAt: result.lastSeenAt, lastSeenMs: result.lastSeenAt ? (Date.now() - new Date(result.lastSeenAt).valueOf()) : null, status: result.state || 'offline', + onlineStatus: device.status, mode: result.mode || 'autonomous', isDeploying: app.db.controllers.Device.isDeploying(device), links: result.links @@ -163,11 +168,12 @@ module.exports = function (app) { lastSeenAt: { nullable: true, type: 'string' }, lastSeenMs: { nullable: true, type: 'number' }, status: { type: 'string' }, + onlineStatus: { type: 'string', enum: ['online', 'offline', 'not-seen'] }, mode: { type: 'string' }, isDeploying: { type: 'boolean' }, editor: { type: 'object', additionalProperties: true } }, - required: ['id', 'lastSeenAt', 'lastSeenMs', 'status', 'mode', 'isDeploying'], + required: ['id', 'lastSeenAt', 'lastSeenMs', 'status', 'onlineStatus', 'mode', 'isDeploying'], additionalProperties: false }) app.addSchema({ @@ -186,6 +192,7 @@ module.exports = function (app) { lastSeenAt: summary.lastSeenAt, lastSeenMs: summary.lastSeenMs, status: summary.status, + onlineStatus: summary.onlineStatus, mode: summary.mode, isDeploying: summary.isDeploying } diff --git a/frontend/src/components/DevicesBrowser.vue b/frontend/src/components/DevicesBrowser.vue index e05e442327..f58fcdd5b3 100644 --- a/frontend/src/components/DevicesBrowser.vue +++ b/frontend/src/components/DevicesBrowser.vue @@ -362,10 +362,9 @@ import DeviceLink from '../pages/application/components/cells/DeviceLink.vue' import Snapshot from '../pages/application/components/cells/Snapshot.vue' import DeviceCreatedAtCell from '../pages/device/DeviceCreatedAtCell.vue' -import DeviceLastSeenCell from '../pages/device/components/DeviceLastSeenCell.vue' import DeviceModeBadge from '../pages/device/components/DeviceModeBadge.vue' +import DeviceOnlineStatusCell from '../pages/device/components/DeviceOnlineStatusCell.vue' import SnapshotAssignDialog from '../pages/instance/VersionHistory/Snapshots/dialogs/SnapshotAssignDialog.vue' -import InstanceStatusBadge from '../pages/instance/components/InstanceStatusBadge.vue' import DeviceAssignApplicationDialog from '../pages/team/Devices/dialogs/DeviceAssignApplicationDialog.vue' import DeviceAssignInstanceDialog from '../pages/team/Devices/dialogs/DeviceAssignInstanceDialog.vue' import DeviceCredentialsDialog from '../pages/team/Devices/dialogs/DeviceCredentialsDialog.vue' @@ -478,9 +477,8 @@ export default { { label: 'Remote Instance', key: 'name', sortable: true, component: { is: markRaw(DeviceLink) } }, { label: 'Type', key: 'type', class: ['w-48'], sortable: true }, { label: 'Created', key: 'createdAt', class: ['w-48'], sortable: true, component: { is: markRaw(DeviceCreatedAtCell) } }, - { label: 'Last Seen', key: 'lastSeenAt', class: ['w-48'], sortable: true, component: { is: markRaw(DeviceLastSeenCell) } }, { label: 'Mode', key: 'mode', class: ['w-30'], sortable: true, component: { is: markRaw(DeviceModeBadge) } }, - { label: 'Last Known Status', class: ['w-32'], component: { is: markRaw(InstanceStatusBadge), map: { instanceId: 'id' }, extraProps: { instanceType: 'device' } } } + { label: 'Status', key: 'lastSeenAt', class: ['w-40'], sortable: true, component: { is: markRaw(DeviceOnlineStatusCell) } } ] if (this.displayingTeam) { diff --git a/frontend/src/index.css b/frontend/src/index.css index 2afdce355d..30f7bf02c9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -297,11 +297,23 @@ } .forge-status-success, .forge-status-connected, - .forge-status-running { + .forge-status-running, + .forge-status-online { background-color: var(--ff-color-status-success-bg); border-color: var(--ff-color-status-success-border); color: var(--ff-color-status-success-text); } + .forge-status-offline { + background-color: var(--ff-color-status-neutral-bg); + border-color: var(--ff-color-status-neutral-border); + color: var(--ff-color-status-neutral-text); + } + .forge-status-not-seen { + background-color: var(--ff-color-status-neutral-bg); + border-color: var(--ff-color-status-neutral-border); + color: var(--ff-color-status-neutral-text); + @apply border-dashed; + } .forge-minimal-status-error, .forge-minimal-status-crashed { diff --git a/frontend/src/pages/device/components/DeviceOnlineStatusCell.vue b/frontend/src/pages/device/components/DeviceOnlineStatusCell.vue new file mode 100644 index 0000000000..17686b0073 --- /dev/null +++ b/frontend/src/pages/device/components/DeviceOnlineStatusCell.vue @@ -0,0 +1,49 @@ + + + diff --git a/frontend/src/types/generated.ts b/frontend/src/types/generated.ts index 14fdab1d89..e44ebba3a9 100644 --- a/frontend/src/types/generated.ts +++ b/frontend/src/types/generated.ts @@ -10394,6 +10394,8 @@ export interface components { activeSnapshot?: components["schemas"]["SnapshotSummary"] | null; targetSnapshot?: components["schemas"]["SnapshotSummary"] | null; status: string; + /** @enum {string} */ + onlineStatus: "online" | "offline" | "not-seen"; isDeploying: boolean; agentVersion?: string | null; mode: string; @@ -10417,6 +10419,8 @@ export interface components { lastSeenAt: string | null; lastSeenMs: number | null; status: string; + /** @enum {string} */ + onlineStatus: "online" | "offline" | "not-seen"; mode: string; isDeploying: boolean; links: components["schemas"]["LinksMeta"]; @@ -10432,6 +10436,8 @@ export interface components { lastSeenAt: string | null; lastSeenMs: number | null; status: string; + /** @enum {string} */ + onlineStatus: "online" | "offline" | "not-seen"; mode: string; isDeploying: boolean; editor?: {