mirror of https://github.com/portainer/portainer
fix(homepage) move heartbeat logic to backend EE-5317 (#8736)
parent
3118c639f6
commit
b4dbc341cc
|
@ -37,6 +37,7 @@
|
||||||
"EdgeKey": "",
|
"EdgeKey": "",
|
||||||
"Extensions": [],
|
"Extensions": [],
|
||||||
"GroupId": 1,
|
"GroupId": 1,
|
||||||
|
"Heartbeat": false,
|
||||||
"Id": 1,
|
"Id": 1,
|
||||||
"Name": "local",
|
"Name": "local",
|
||||||
"PublicURL": "",
|
"PublicURL": "",
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"EnableGPUManagement": false,
|
"EnableGPUManagement": false,
|
||||||
"Gpus": [],
|
"Gpus": [],
|
||||||
"GroupId": 1,
|
"GroupId": 1,
|
||||||
|
"Heartbeat": false,
|
||||||
"Id": 1,
|
"Id": 1,
|
||||||
"IsEdgeDevice": false,
|
"IsEdgeDevice": false,
|
||||||
"Kubernetes": {
|
"Kubernetes": {
|
||||||
|
|
|
@ -42,7 +42,13 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request)
|
||||||
return httperror.Forbidden("Permission denied to access environment", err)
|
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)
|
hideFields(endpoint)
|
||||||
|
endpointutils.UpdateEdgeEndpointHeartbeat(endpoint, settings)
|
||||||
endpoint.ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion()
|
endpoint.ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion()
|
||||||
|
|
||||||
if !excludeSnapshot(r) {
|
if !excludeSnapshot(r) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/response"
|
"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].EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||||
}
|
}
|
||||||
paginatedEndpoints[idx].QueryDate = time.Now().Unix()
|
paginatedEndpoints[idx].QueryDate = time.Now().Unix()
|
||||||
|
endpointutils.UpdateEdgeEndpointHeartbeat(&paginatedEndpoints[idx], settings)
|
||||||
if !query.excludeSnapshots {
|
if !query.excludeSnapshots {
|
||||||
err = handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx])
|
err = handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -197,3 +197,39 @@ func InitialStorageDetection(endpoint *portainer.Endpoint, endpointService datas
|
||||||
log.Err(err).Msg("final error while detecting storage classes")
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -388,6 +388,9 @@ type (
|
||||||
LastCheckInDate int64
|
LastCheckInDate int64
|
||||||
// QueryDate of each query with the endpoints list
|
// QueryDate of each query with the endpoints list
|
||||||
QueryDate int64
|
QueryDate int64
|
||||||
|
// Heartbeat indicates the heartbeat status of an edge environment
|
||||||
|
Heartbeat bool `json:"Heartbeat" example:"true"`
|
||||||
|
|
||||||
// IsEdgeDevice marks if the environment was created as an EdgeDevice
|
// IsEdgeDevice marks if the environment was created as an EdgeDevice
|
||||||
IsEdgeDevice bool
|
IsEdgeDevice bool
|
||||||
// Whether the device has been trusted or not by the user
|
// Whether the device has been trusted or not by the user
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Activity } from 'lucide-react';
|
import { Activity } from 'lucide-react';
|
||||||
|
|
||||||
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
|
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
|
||||||
import { useHasHeartbeat } from '@/react/edge/hooks/useHasHeartbeat';
|
|
||||||
import { Environment } from '@/react/portainer/environments/types';
|
import { Environment } from '@/react/portainer/environments/types';
|
||||||
|
|
||||||
import { EnvironmentStatusBadgeItem } from './EnvironmentStatusBadgeItem';
|
import { EnvironmentStatusBadgeItem } from './EnvironmentStatusBadgeItem';
|
||||||
|
@ -13,14 +12,9 @@ interface Props {
|
||||||
|
|
||||||
export function EdgeIndicator({
|
export function EdgeIndicator({
|
||||||
environment,
|
environment,
|
||||||
|
|
||||||
showLastCheckInDate = false,
|
showLastCheckInDate = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const isValid = useHasHeartbeat(environment);
|
const heartbeat = environment.Heartbeat;
|
||||||
|
|
||||||
if (isValid === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const associated = !!environment.EdgeID;
|
const associated = !!environment.EdgeID;
|
||||||
if (!associated) {
|
if (!associated) {
|
||||||
|
@ -40,8 +34,8 @@ export function EdgeIndicator({
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<EnvironmentStatusBadgeItem
|
<EnvironmentStatusBadgeItem
|
||||||
color={isValid ? 'success' : 'danger'}
|
color={heartbeat ? 'success' : 'danger'}
|
||||||
icon={isValid ? 'svg-heartbeatup' : 'svg-heartbeatdown'}
|
icon={heartbeat ? 'svg-heartbeatup' : 'svg-heartbeatdown'}
|
||||||
aria-label="edge-heartbeat"
|
aria-label="edge-heartbeat"
|
||||||
>
|
>
|
||||||
heartbeat
|
heartbeat
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -144,6 +144,7 @@ export type Environment = {
|
||||||
EdgeKey: string;
|
EdgeKey: string;
|
||||||
EdgeCheckinInterval?: number;
|
EdgeCheckinInterval?: number;
|
||||||
QueryDate?: number;
|
QueryDate?: number;
|
||||||
|
Heartbeat?: boolean;
|
||||||
LastCheckInDate?: number;
|
LastCheckInDate?: number;
|
||||||
Name: string;
|
Name: string;
|
||||||
Status: EnvironmentStatus;
|
Status: EnvironmentStatus;
|
||||||
|
|
Loading…
Reference in New Issue