From b00aa68c2b119ac8d8ad912a6d0ab0f016a45cd4 Mon Sep 17 00:00:00 2001 From: cmeng Date: Thu, 6 Apr 2023 09:09:22 +1200 Subject: [PATCH] fix(homepage) move heartbeat logic to backend EE-5317 (#8737) --- api/datastore/test_data/input_24.json | 1 + .../test_data/output_24_to_latest.json | 1 + .../handler/endpoints/endpoint_inspect.go | 6 ++ api/http/handler/endpoints/endpoint_list.go | 2 + api/internal/endpointutils/endpointutils.go | 36 +++++++++++ api/portainer.go | 2 + app/react/components/EdgeIndicator.tsx | 12 +--- app/react/edge/hooks/useHasHeartbeat.ts | 61 ------------------- app/react/portainer/environments/types.ts | 1 + 9 files changed, 52 insertions(+), 70 deletions(-) delete mode 100644 app/react/edge/hooks/useHasHeartbeat.ts diff --git a/api/datastore/test_data/input_24.json b/api/datastore/test_data/input_24.json index 889f80c83..a4f837477 100644 --- a/api/datastore/test_data/input_24.json +++ b/api/datastore/test_data/input_24.json @@ -37,6 +37,7 @@ "EdgeKey": "", "Extensions": [], "GroupId": 1, + "Heartbeat": false, "Id": 1, "Name": "local", "PublicURL": "", diff --git a/api/datastore/test_data/output_24_to_latest.json b/api/datastore/test_data/output_24_to_latest.json index 9ef6f3d01..256fd1aae 100644 --- a/api/datastore/test_data/output_24_to_latest.json +++ b/api/datastore/test_data/output_24_to_latest.json @@ -49,6 +49,7 @@ "EnableGPUManagement": false, "Gpus": [], "GroupId": 1, + "Heartbeat": false, "Id": 1, "IsEdgeDevice": false, "Kubernetes": { diff --git a/api/http/handler/endpoints/endpoint_inspect.go b/api/http/handler/endpoints/endpoint_inspect.go index 4546e4499..8a495de2f 100644 --- a/api/http/handler/endpoints/endpoint_inspect.go +++ b/api/http/handler/endpoints/endpoint_inspect.go @@ -42,7 +42,13 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request) return httperror.Forbidden("Permission denied to access environment", err) } + settings, err := handler.DataStore.Settings().Settings() + if err != nil { + return httperror.InternalServerError("Unable to retrieve settings from the database", err) + } + hideFields(endpoint) + endpointutils.UpdateEdgeEndpointHeartbeat(endpoint, settings) endpoint.ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion() if !excludeSnapshot(r) { diff --git a/api/http/handler/endpoints/endpoint_list.go b/api/http/handler/endpoints/endpoint_list.go index a6758882a..8ab1a35cf 100644 --- a/api/http/handler/endpoints/endpoint_list.go +++ b/api/http/handler/endpoints/endpoint_list.go @@ -9,6 +9,7 @@ import ( "github.com/portainer/libhttp/request" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/security" + "github.com/portainer/portainer/api/internal/endpointutils" httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/response" @@ -103,6 +104,7 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht paginatedEndpoints[idx].EdgeCheckinInterval = settings.EdgeAgentCheckinInterval } paginatedEndpoints[idx].QueryDate = time.Now().Unix() + endpointutils.UpdateEdgeEndpointHeartbeat(&paginatedEndpoints[idx], settings) if !query.excludeSnapshots { err = handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx]) if err != nil { diff --git a/api/internal/endpointutils/endpointutils.go b/api/internal/endpointutils/endpointutils.go index 918217ff3..d2b9b98d8 100644 --- a/api/internal/endpointutils/endpointutils.go +++ b/api/internal/endpointutils/endpointutils.go @@ -197,3 +197,39 @@ func InitialStorageDetection(endpoint *portainer.Endpoint, endpointService datas log.Err(err).Msg("final error while detecting storage classes") }() } + +func UpdateEdgeEndpointHeartbeat(endpoint *portainer.Endpoint, settings *portainer.Settings) { + if IsEdgeEndpoint(endpoint) { + checkInInterval := getEndpointCheckinInterval(endpoint, settings) + endpoint.Heartbeat = endpoint.QueryDate-endpoint.LastCheckInDate <= int64(checkInInterval*2+20) + } +} + +func getEndpointCheckinInterval(endpoint *portainer.Endpoint, settings *portainer.Settings) int { + if endpoint.Edge.AsyncMode { + defaultInterval := 60 + intervals := [][]int{ + {endpoint.Edge.PingInterval, settings.Edge.PingInterval}, + {endpoint.Edge.CommandInterval, settings.Edge.CommandInterval}, + {endpoint.Edge.SnapshotInterval, settings.Edge.SnapshotInterval}, + } + + for i := 0; i < len(intervals); i++ { + effectiveInterval := intervals[i][0] + if effectiveInterval <= 0 { + effectiveInterval = intervals[i][1] + } + if effectiveInterval > 0 && effectiveInterval < defaultInterval { + defaultInterval = effectiveInterval + } + } + + return defaultInterval + } + + if endpoint.EdgeCheckinInterval > 0 { + return endpoint.EdgeCheckinInterval + } + + return settings.EdgeAgentCheckinInterval +} diff --git a/api/portainer.go b/api/portainer.go index ade4b73bb..0d1771cfb 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -391,6 +391,8 @@ type ( LastCheckInDate int64 // QueryDate of each query with the endpoints list QueryDate int64 + // Heartbeat indicates the heartbeat status of an edge environment + Heartbeat bool `json:"Heartbeat" example:"true"` // Whether the device has been trusted or not by the user UserTrusted bool diff --git a/app/react/components/EdgeIndicator.tsx b/app/react/components/EdgeIndicator.tsx index 7ab2e6753..12aff5626 100644 --- a/app/react/components/EdgeIndicator.tsx +++ b/app/react/components/EdgeIndicator.tsx @@ -1,7 +1,6 @@ import { Activity } from 'lucide-react'; import { isoDateFromTimestamp } from '@/portainer/filters/filters'; -import { useHasHeartbeat } from '@/react/edge/hooks/useHasHeartbeat'; import { Environment } from '@/react/portainer/environments/types'; import { EnvironmentStatusBadgeItem } from './EnvironmentStatusBadgeItem'; @@ -13,14 +12,9 @@ interface Props { export function EdgeIndicator({ environment, - showLastCheckInDate = false, }: Props) { - const isValid = useHasHeartbeat(environment); - - if (isValid === null) { - return null; - } + const heartbeat = environment.Heartbeat; const associated = !!environment.EdgeID; if (!associated) { @@ -40,8 +34,8 @@ export function EdgeIndicator({ className="flex items-center gap-1" > heartbeat diff --git a/app/react/edge/hooks/useHasHeartbeat.ts b/app/react/edge/hooks/useHasHeartbeat.ts deleted file mode 100644 index e55f594e0..000000000 --- a/app/react/edge/hooks/useHasHeartbeat.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Environment } from '@/react/portainer/environments/types'; -import { usePublicSettings } from '@/react/portainer/settings/queries'; -import { PublicSettingsResponse } from '@/react/portainer/settings/types'; - -export function useHasHeartbeat(environment: Environment) { - const associated = !!environment.EdgeID; - - const settingsQuery = usePublicSettings({ enabled: associated }); - - if (!associated) { - return false; - } - - const { LastCheckInDate, QueryDate } = environment; - - const settings = settingsQuery.data; - - if (!settings) { - return null; - } - - const checkInInterval = getCheckinInterval(environment, settings); - - if (checkInInterval && QueryDate && LastCheckInDate) { - return QueryDate - LastCheckInDate <= checkInInterval * 2 + 20; - } - - return false; -} - -function getCheckinInterval( - environment: Environment, - settings: PublicSettingsResponse -) { - const asyncMode = environment.Edge.AsyncMode; - - if (asyncMode) { - const intervals = [ - environment.Edge.PingInterval > 0 - ? environment.Edge.PingInterval - : settings.Edge.PingInterval, - environment.Edge.SnapshotInterval > 0 - ? environment.Edge.SnapshotInterval - : settings.Edge.SnapshotInterval, - environment.Edge.CommandInterval > 0 - ? environment.Edge.CommandInterval - : settings.Edge.CommandInterval, - ].filter((n) => n > 0); - - return intervals.length > 0 ? Math.min(...intervals) : 60; - } - - if ( - !environment.EdgeCheckinInterval || - environment.EdgeCheckinInterval === 0 - ) { - return settings.Edge.CheckinInterval; - } - - return environment.EdgeCheckinInterval; -} diff --git a/app/react/portainer/environments/types.ts b/app/react/portainer/environments/types.ts index bba78ec4d..27e960a34 100644 --- a/app/react/portainer/environments/types.ts +++ b/app/react/portainer/environments/types.ts @@ -144,6 +144,7 @@ export type Environment = { EdgeKey: string; EdgeCheckinInterval?: number; QueryDate?: number; + Heartbeat?: boolean; LastCheckInDate?: number; Name: string; Status: EnvironmentStatus;