mirror of https://github.com/portainer/portainer
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
parent
f2faccdb10
commit
91ff7e4143
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
|
@ -100,12 +101,14 @@ func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Req
|
||||||
} else if agentPlatform == portainer.AgentPlatformKubernetes {
|
} else if agentPlatform == portainer.AgentPlatformKubernetes {
|
||||||
endpoint.Type = portainer.EdgeAgentOnKubernetesEnvironment
|
endpoint.Type = portainer.EdgeAgentOnKubernetesEnvironment
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint.LastCheckInDate = time.Now().Unix()
|
||||||
|
|
||||||
err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
|
err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist endpoint changes inside the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist endpoint changes inside the database", err}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
settings, err := handler.DataStore.Settings().Settings()
|
settings, err := handler.DataStore.Settings().Settings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -249,6 +249,8 @@ type (
|
||||||
ComposeSyntaxMaxVersion string `json:"ComposeSyntaxMaxVersion" example:"3.8"`
|
ComposeSyntaxMaxVersion string `json:"ComposeSyntaxMaxVersion" example:"3.8"`
|
||||||
// Endpoint specific security settings
|
// Endpoint specific security settings
|
||||||
SecuritySettings EndpointSecuritySettings
|
SecuritySettings EndpointSecuritySettings
|
||||||
|
// LastCheckInDate mark last check-in date on checkin
|
||||||
|
LastCheckInDate int64
|
||||||
|
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
// Deprecated in DBVersion == 4
|
// Deprecated in DBVersion == 4
|
||||||
|
@ -339,7 +341,7 @@ type (
|
||||||
// EndpointType represents the type of an endpoint
|
// EndpointType represents the type of an endpoint
|
||||||
EndpointType int
|
EndpointType int
|
||||||
|
|
||||||
// EndpointRelation represnts a endpoint relation object
|
// EndpointRelation represents a endpoint relation object
|
||||||
EndpointRelation struct {
|
EndpointRelation struct {
|
||||||
EndpointID EndpointID
|
EndpointID EndpointID
|
||||||
EdgeStacks map[EdgeStackID]bool
|
EdgeStacks map[EdgeStackID]bool
|
||||||
|
@ -1182,7 +1184,7 @@ type (
|
||||||
DeleteResourceControl(ID ResourceControlID) error
|
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 {
|
ReverseTunnelService interface {
|
||||||
StartTunnelServer(addr, port string, snapshotService SnapshotService) error
|
StartTunnelServer(addr, port string, snapshotService SnapshotService) error
|
||||||
GenerateEdgeKey(url, host string, endpointIdentifier int) string
|
GenerateEdgeKey(url, host string, endpointIdentifier int) string
|
||||||
|
@ -1224,7 +1226,7 @@ type (
|
||||||
GetNextIdentifier() int
|
GetNextIdentifier() int
|
||||||
}
|
}
|
||||||
|
|
||||||
// StackService represents a service for managing endpoint snapshots
|
// SnapshotService represents a service for managing endpoint snapshots
|
||||||
SnapshotService interface {
|
SnapshotService interface {
|
||||||
Start()
|
Start()
|
||||||
SetSnapshotInterval(snapshotInterval string) error
|
SetSnapshotInterval(snapshotInterval string) error
|
||||||
|
@ -1547,6 +1549,7 @@ const (
|
||||||
EdgeAgentActive string = "ACTIVE"
|
EdgeAgentActive string = "ACTIVE"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// represents an authorization type
|
||||||
const (
|
const (
|
||||||
OperationDockerContainerArchiveInfo Authorization = "DockerContainerArchiveInfo"
|
OperationDockerContainerArchiveInfo Authorization = "DockerContainerArchiveInfo"
|
||||||
OperationDockerContainerList Authorization = "DockerContainerList"
|
OperationDockerContainerList Authorization = "DockerContainerList"
|
||||||
|
|
|
@ -26,8 +26,24 @@ class EndpointItemController {
|
||||||
return _.join(tagNames, ',');
|
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() {
|
$onInit() {
|
||||||
this.endpointTags = this.joinTags();
|
this.endpointTags = this.joinTags();
|
||||||
|
this.isCheckInValid = this.calcIsCheckInValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
$onChanges({ tags, model }) {
|
$onChanges({ tags, model }) {
|
||||||
|
@ -35,6 +51,10 @@ class EndpointItemController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.endpointTags = this.joinTags();
|
this.endpointTags = this.joinTags();
|
||||||
|
|
||||||
|
if (model) {
|
||||||
|
this.isCheckInValid = this.calcIsCheckInValid();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,18 @@
|
||||||
{{ $ctrl.model.Name }}
|
{{ $ctrl.model.Name }}
|
||||||
</span>
|
</span>
|
||||||
<span class="space-left blocklist-item-subtitle">
|
<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.isEdgeEndpoint()">
|
||||||
<span ng-if="$ctrl.model.EdgeID"><i class="fas fa-link"></i> associated</span>
|
<span ng-if="!$ctrl.model.EdgeID" class="label label-default"><s>associated</s></span>
|
||||||
<span ng-if="!$ctrl.model.EdgeID"><i class="fas fa-unlink"></i> <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 class="label label-{{ $ctrl.model.Status | endpointstatusbadge }}" ng-if="$ctrl.model.Type !== 4 && $ctrl.model.Type !== 7">
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span ng-if="!$ctrl.isEdgeEndpoint()">
|
||||||
|
<span class="label label-{{ $ctrl.model.Status | endpointstatusbadge }}">
|
||||||
{{ $ctrl.model.Status === 1 ? 'up' : 'down' }}
|
{{ $ctrl.model.Status === 1 ? 'up' : 'down' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="space-left small text-muted" ng-if="$ctrl.model.Snapshots[0]">
|
<span class="space-left small text-muted" ng-if="$ctrl.model.Snapshots[0]">
|
||||||
|
@ -34,6 +41,7 @@
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<span class="small" ng-if="$ctrl.model.GroupName"> Group: {{ $ctrl.model.GroupName }} </span>
|
<span class="small" ng-if="$ctrl.model.GroupName"> Group: {{ $ctrl.model.GroupName }} </span>
|
||||||
<button ng-if="$ctrl.isAdmin" class="btn btn-link btn-xs" ng-click="$ctrl.editEndpoint($event)"><i class="fa fa-pencil-alt"></i> </button>
|
<button ng-if="$ctrl.isAdmin" class="btn btn-link btn-xs" ng-click="$ctrl.editEndpoint($event)"><i class="fa fa-pencil-alt"></i> </button>
|
||||||
|
|
|
@ -13,7 +13,8 @@ angular
|
||||||
EndpointProvider,
|
EndpointProvider,
|
||||||
StateManager,
|
StateManager,
|
||||||
ModalService,
|
ModalService,
|
||||||
MotdService
|
MotdService,
|
||||||
|
SettingsService
|
||||||
) {
|
) {
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
connectingToEdgeEndpoint: false,
|
connectingToEdgeEndpoint: false,
|
||||||
|
@ -82,7 +83,7 @@ angular
|
||||||
var groups = data.groups;
|
var groups = data.groups;
|
||||||
EndpointHelper.mapGroupNameToEndpoint(endpoints, groups);
|
EndpointHelper.mapGroupNameToEndpoint(endpoints, groups);
|
||||||
EndpointProvider.setEndpoints(endpoints);
|
EndpointProvider.setEndpoints(endpoints);
|
||||||
deferred.resolve({ endpoints: endpoints, totalCount: data.endpoints.totalCount });
|
deferred.resolve({ endpoints: decorateEndpoints(endpoints), totalCount: data.endpoints.totalCount });
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve endpoint information');
|
Notifications.error('Failure', err, 'Unable to retrieve endpoint information');
|
||||||
|
@ -98,14 +99,15 @@ angular
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
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.tags = tags;
|
||||||
|
$scope.defaultEdgeCheckInInterval = settings.EdgeAgentCheckinInterval;
|
||||||
|
|
||||||
$scope.totalCount = totalCount;
|
$scope.totalCount = totalCount;
|
||||||
if (totalCount > 100) {
|
if (totalCount > 100) {
|
||||||
$scope.endpoints = [];
|
$scope.endpoints = [];
|
||||||
} else {
|
} else {
|
||||||
$scope.endpoints = endpoints;
|
$scope.endpoints = decorateEndpoints(endpoints);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Notifications.error('Failed loading page data', err);
|
Notifications.error('Failed loading page data', err);
|
||||||
|
@ -113,4 +115,10 @@ angular
|
||||||
}
|
}
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
|
|
||||||
|
function decorateEndpoints(endpoints) {
|
||||||
|
return endpoints.map((endpoint) => {
|
||||||
|
return { ...endpoint, EdgeCheckinInterval: endpoint.EdgeAgentCheckinInterval || $scope.defaultEdgeCheckInInterval };
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue