fix(homepage) move heartbeat logic to backend EE-5317 (#8737)

pull/8746/head
cmeng 2023-04-06 09:09:22 +12:00 committed by GitHub
parent 8c5edd2c97
commit b00aa68c2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 52 additions and 70 deletions

View File

@ -37,6 +37,7 @@
"EdgeKey": "",
"Extensions": [],
"GroupId": 1,
"Heartbeat": false,
"Id": 1,
"Name": "local",
"PublicURL": "",

View File

@ -49,6 +49,7 @@
"EnableGPUManagement": false,
"Gpus": [],
"GroupId": 1,
"Heartbeat": false,
"Id": 1,
"IsEdgeDevice": false,
"Kubernetes": {

View File

@ -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) {

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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"
>
<EnvironmentStatusBadgeItem
color={isValid ? 'success' : 'danger'}
icon={isValid ? 'svg-heartbeatup' : 'svg-heartbeatdown'}
color={heartbeat ? 'success' : 'danger'}
icon={heartbeat ? 'svg-heartbeatup' : 'svg-heartbeatdown'}
aria-label="edge-heartbeat"
>
heartbeat

View File

@ -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;
}

View File

@ -144,6 +144,7 @@ export type Environment = {
EdgeKey: string;
EdgeCheckinInterval?: number;
QueryDate?: number;
Heartbeat?: boolean;
LastCheckInDate?: number;
Name: string;
Status: EnvironmentStatus;