feat(edge): show last check in date (#4782)

* feat(k8s): better form validation for configuration keys (#4728) (#4733)

Co-authored-by: Simon Meng <simon.meng@portainer.io>

* feat(home): show edge valid tag

* fix(endpoint): show right heartbeat

* style(endpoints): add some comments

Co-authored-by: cong meng <mcpacino@gmail.com>
Co-authored-by: Simon Meng <simon.meng@portainer.io>
pull/4868/head
Chaim Lev-Ari 2021-03-01 02:43:47 +02:00 committed by GitHub
parent f2faccdb10
commit 91ff7e4143
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 22 deletions

View File

@ -5,6 +5,7 @@ import (
"errors"
"net/http"
"strconv"
"time"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
@ -100,11 +101,13 @@ func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Req
} else if agentPlatform == portainer.AgentPlatformKubernetes {
endpoint.Type = portainer.EdgeAgentOnKubernetesEnvironment
}
}
err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist endpoint changes inside the database", err}
}
endpoint.LastCheckInDate = time.Now().Unix()
err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist endpoint changes inside the database", err}
}
settings, err := handler.DataStore.Settings().Settings()

View File

@ -249,6 +249,8 @@ type (
ComposeSyntaxMaxVersion string `json:"ComposeSyntaxMaxVersion" example:"3.8"`
// Endpoint specific security settings
SecuritySettings EndpointSecuritySettings
// LastCheckInDate mark last check-in date on checkin
LastCheckInDate int64
// Deprecated fields
// Deprecated in DBVersion == 4
@ -339,7 +341,7 @@ type (
// EndpointType represents the type of an endpoint
EndpointType int
// EndpointRelation represnts a endpoint relation object
// EndpointRelation represents a endpoint relation object
EndpointRelation struct {
EndpointID EndpointID
EdgeStacks map[EdgeStackID]bool
@ -1182,7 +1184,7 @@ type (
DeleteResourceControl(ID ResourceControlID) error
}
// ReverseTunnelService represensts a service used to manage reverse tunnel connections.
// ReverseTunnelService represents a service used to manage reverse tunnel connections.
ReverseTunnelService interface {
StartTunnelServer(addr, port string, snapshotService SnapshotService) error
GenerateEdgeKey(url, host string, endpointIdentifier int) string
@ -1224,7 +1226,7 @@ type (
GetNextIdentifier() int
}
// StackService represents a service for managing endpoint snapshots
// SnapshotService represents a service for managing endpoint snapshots
SnapshotService interface {
Start()
SetSnapshotInterval(snapshotInterval string) error
@ -1547,6 +1549,7 @@ const (
EdgeAgentActive string = "ACTIVE"
)
// represents an authorization type
const (
OperationDockerContainerArchiveInfo Authorization = "DockerContainerArchiveInfo"
OperationDockerContainerList Authorization = "DockerContainerList"

View File

@ -26,8 +26,24 @@ class EndpointItemController {
return _.join(tagNames, ',');
}
isEdgeEndpoint() {
return this.model.Type === 4 || this.model.Type === 7;
}
calcIsCheckInValid() {
if (!this.isEdgeEndpoint()) {
return false;
}
const checkInInterval = this.model.EdgeCheckinInterval;
const now = Math.floor(Date.now() / 1000);
// give checkIn some wiggle room
return now - this.model.LastCheckInDate <= checkInInterval * 2;
}
$onInit() {
this.endpointTags = this.joinTags();
this.isCheckInValid = this.calcIsCheckInValid();
}
$onChanges({ tags, model }) {
@ -35,6 +51,10 @@ class EndpointItemController {
return;
}
this.endpointTags = this.joinTags();
if (model) {
this.isCheckInValid = this.calcIsCheckInValid();
}
}
}

View File

@ -19,18 +19,26 @@
{{ $ctrl.model.Name }}
</span>
<span class="space-left blocklist-item-subtitle">
<span ng-if="$ctrl.model.Type === 4 || $ctrl.model.Type === 7" class="small text-muted">
<span ng-if="$ctrl.model.EdgeID"><i class="fas fa-link"></i> associated</span>
<span ng-if="!$ctrl.model.EdgeID"><i class="fas fa-unlink"></i> <s>associated</s></span>
<span ng-if="$ctrl.isEdgeEndpoint()">
<span ng-if="!$ctrl.model.EdgeID" class="label label-default"><s>associated</s></span>
<span ng-if="$ctrl.model.EdgeID">
<span class="label" ng-class="{ 'label-danger': !$ctrl.isCheckInValid, 'label-success': $ctrl.isCheckInValid }">heartbeat</span>
<span class="space-left small text-muted" ng-if="$ctrl.model.LastCheckInDate">
{{ $ctrl.model.LastCheckInDate | getisodatefromtimestamp }}
</span>
</span>
</span>
<span class="label label-{{ $ctrl.model.Status | endpointstatusbadge }}" ng-if="$ctrl.model.Type !== 4 && $ctrl.model.Type !== 7">
{{ $ctrl.model.Status === 1 ? 'up' : 'down' }}
</span>
<span class="space-left small text-muted" ng-if="$ctrl.model.Snapshots[0]">
{{ $ctrl.model.Snapshots[0].Time | getisodatefromtimestamp }}
</span>
<span class="space-left small text-muted" ng-if="$ctrl.model.Kubernetes.Snapshots[0]">
{{ $ctrl.model.Kubernetes.Snapshots[0].Time | getisodatefromtimestamp }}
<span ng-if="!$ctrl.isEdgeEndpoint()">
<span class="label label-{{ $ctrl.model.Status | endpointstatusbadge }}">
{{ $ctrl.model.Status === 1 ? 'up' : 'down' }}
</span>
<span class="space-left small text-muted" ng-if="$ctrl.model.Snapshots[0]">
{{ $ctrl.model.Snapshots[0].Time | getisodatefromtimestamp }}
</span>
<span class="space-left small text-muted" ng-if="$ctrl.model.Kubernetes.Snapshots[0]">
{{ $ctrl.model.Kubernetes.Snapshots[0].Time | getisodatefromtimestamp }}
</span>
</span>
</span>
</span>

View File

@ -13,7 +13,8 @@ angular
EndpointProvider,
StateManager,
ModalService,
MotdService
MotdService,
SettingsService
) {
$scope.state = {
connectingToEdgeEndpoint: false,
@ -82,7 +83,7 @@ angular
var groups = data.groups;
EndpointHelper.mapGroupNameToEndpoint(endpoints, groups);
EndpointProvider.setEndpoints(endpoints);
deferred.resolve({ endpoints: endpoints, totalCount: data.endpoints.totalCount });
deferred.resolve({ endpoints: decorateEndpoints(endpoints), totalCount: data.endpoints.totalCount });
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoint information');
@ -98,14 +99,15 @@ angular
});
try {
const [{ totalCount, endpoints }, tags] = await Promise.all([getPaginatedEndpoints(0, 100), TagService.tags()]);
const [{ totalCount, endpoints }, tags, settings] = await Promise.all([getPaginatedEndpoints(0, 100), TagService.tags(), SettingsService.settings()]);
$scope.tags = tags;
$scope.defaultEdgeCheckInInterval = settings.EdgeAgentCheckinInterval;
$scope.totalCount = totalCount;
if (totalCount > 100) {
$scope.endpoints = [];
} else {
$scope.endpoints = endpoints;
$scope.endpoints = decorateEndpoints(endpoints);
}
} catch (err) {
Notifications.error('Failed loading page data', err);
@ -113,4 +115,10 @@ angular
}
initView();
function decorateEndpoints(endpoints) {
return endpoints.map((endpoint) => {
return { ...endpoint, EdgeCheckinInterval: endpoint.EdgeAgentCheckinInterval || $scope.defaultEdgeCheckInInterval };
});
}
});