feat(waiting-room): show and filter by check in [EE-5186] (#8701)

pull/8819/head
Chaim Lev-Ari 2023-04-27 09:22:05 +07:00 committed by GitHub
parent b5771df6a8
commit 4b9c857d85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 108 additions and 25 deletions

View File

@ -34,7 +34,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
idxEdgeID: make(map[string]portainer.EndpointID),
}
es, err := s.Endpoints()
es, err := s.endpoints()
if err != nil {
return nil, err
}
@ -89,8 +89,7 @@ func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
})
}
// Endpoints return an array containing all the environments(endpoints).
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
func (service *Service) endpoints() ([]portainer.Endpoint, error) {
var endpoints []portainer.Endpoint
var err error
@ -99,8 +98,14 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
return err
})
return endpoints, err
}
// Endpoints return an array containing all the environments(endpoints).
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
endpoints, err := service.endpoints()
if err != nil {
return endpoints, err
return nil, err
}
for i, e := range endpoints {

View File

@ -117,6 +117,11 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http
return httperror.InternalServerError("Unable to Unable to persist environment changes inside the database", err)
}
err = handler.requestBouncer.TrustedEdgeEnvironmentAccess(endpoint)
if err != nil {
return httperror.Forbidden("Permission denied to access environment", err)
}
checkinInterval := endpoint.EdgeCheckinInterval
if endpoint.EdgeCheckinInterval == 0 {
settings, err := handler.DataStore.Settings().Settings()

View File

@ -45,6 +45,7 @@ const (
// @param agentVersions query []string false "will return only environments with on of these agent versions"
// @param edgeAsync query bool false "if exists true show only edge async agents, false show only standard edge agents. if missing, will show both types (relevant only for edge agents)"
// @param edgeDeviceUntrusted query bool false "if true, show only untrusted edge agents, if false show only trusted edge agents (relevant only for edge agents)"
// @param edgeCheckInPassedSeconds query number false "if bigger then zero, show only edge agents that checked-in in the last provided seconds (relevant only for edge agents)"
// @param name query string false "will return only environments(endpoints) with this name"
// @success 200 {array} portainer.Endpoint "Endpoints"
// @failure 500 "Server error"

View File

@ -23,11 +23,12 @@ type EnvironmentsQuery struct {
groupIds []portainer.EndpointGroupID
status []portainer.EndpointStatus
// if edgeAsync not nil, will filter edge endpoints based on this value
edgeAsync *bool
edgeDeviceUntrusted bool
excludeSnapshots bool
name string
agentVersions []string
edgeAsync *bool
edgeDeviceUntrusted bool
excludeSnapshots bool
name string
agentVersions []string
edgeCheckInPassedSeconds int
}
func parseQuery(r *http.Request) (EnvironmentsQuery, error) {
@ -77,19 +78,22 @@ func parseQuery(r *http.Request) (EnvironmentsQuery, error) {
excludeSnapshots, _ := request.RetrieveBooleanQueryParameter(r, "excludeSnapshots", true)
edgeCheckInPassedSeconds, _ := request.RetrieveNumericQueryParameter(r, "edgeCheckInPassedSeconds", true)
return EnvironmentsQuery{
search: search,
types: endpointTypes,
tagIds: tagIDs,
endpointIds: endpointIDs,
tagsPartialMatch: tagsPartialMatch,
groupIds: groupIDs,
status: status,
edgeAsync: edgeAsync,
edgeDeviceUntrusted: edgeDeviceUntrusted,
excludeSnapshots: excludeSnapshots,
name: name,
agentVersions: agentVersions,
search: search,
types: endpointTypes,
tagIds: tagIDs,
endpointIds: endpointIDs,
tagsPartialMatch: tagsPartialMatch,
groupIds: groupIDs,
status: status,
edgeAsync: edgeAsync,
edgeDeviceUntrusted: edgeDeviceUntrusted,
excludeSnapshots: excludeSnapshots,
name: name,
agentVersions: agentVersions,
edgeCheckInPassedSeconds: edgeCheckInPassedSeconds,
}, nil
}
@ -128,6 +132,22 @@ func (handler *Handler) filterEndpointsByQuery(filteredEndpoints []portainer.End
return endpoint.UserTrusted == !query.edgeDeviceUntrusted
})
if query.edgeCheckInPassedSeconds > 0 {
filteredEndpoints = filter(filteredEndpoints, func(endpoint portainer.Endpoint) bool {
// ignore non-edge endpoints
if !endpointutils.IsEdgeEndpoint(&endpoint) {
return true
}
// filter out endpoints that have never checked in
if endpoint.LastCheckInDate == 0 {
return false
}
return time.Now().Unix()-endpoint.LastCheckInDate < int64(query.edgeCheckInPassedSeconds)
})
}
if len(query.status) > 0 {
filteredEndpoints = filterEndpointsByStatuses(filteredEndpoints, query.status, settings)
}

View File

@ -1,12 +1,11 @@
package security
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/pkg/errors"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
@ -147,13 +146,19 @@ func (bouncer *RequestBouncer) AuthorizedEdgeEndpointOperation(r *http.Request,
return errors.New("invalid Edge identifier")
}
if endpoint.LastCheckInDate > 0 || endpoint.UserTrusted {
return nil
}
// TrustedEdgeEnvironmentAccess defines a security check for Edge environments, checks if
// the request is coming from a trusted Edge environment
func (bouncer *RequestBouncer) TrustedEdgeEnvironmentAccess(endpoint *portainer.Endpoint) error {
if endpoint.UserTrusted {
return nil
}
settings, err := bouncer.dataStore.Settings().Settings()
if err != nil {
return fmt.Errorf("could not retrieve the settings: %w", err)
return errors.WithMessage(err, "could not retrieve the settings")
}
if !settings.TrustOnFirstConnect {

View File

@ -3,8 +3,18 @@ import { useGroups } from '@/react/portainer/environments/environment-groups/que
import { useEdgeGroups } from '@/react/edge/edge-groups/queries/useEdgeGroups';
import { useTags } from '@/portainer/tags/queries';
import { PortainerSelect } from '@@/form-components/PortainerSelect';
import { useFilterStore } from './filter-store';
const checkInOptions = [
{ value: 0, label: 'Show all time' },
{ value: 60 * 60, label: 'Show past hour' },
{ value: 60 * 60 * 24, label: 'Show past day' },
{ value: 60 * 60 * 24 * 7, label: 'Show past week' },
{ value: 60 * 60 * 24 * 14, label: 'Show past 14 days' },
];
export function Filter() {
const edgeGroupsQuery = useEdgeGroups();
const groupsQuery = useGroups();
@ -45,6 +55,14 @@ export function Filter() {
value: g.ID,
}))}
/>
<div className="ml-auto" />
<PortainerSelect
onChange={(f) => filterStore.setCheckIn(f || 0)}
value={filterStore.checkIn}
options={checkInOptions}
bindToBody
/>
</div>
);
}

View File

@ -1,3 +1,4 @@
import moment from 'moment';
import { CellProps, Column } from 'react-table';
import { WaitingRoomEnvironment } from '../types';
@ -52,4 +53,24 @@ export const columns: readonly Column<WaitingRoomEnvironment>[] = [
canHide: false,
sortType: 'string',
},
{
Header: 'Last Check-in',
accessor: 'LastCheckInDate',
Cell: LastCheckinDateCell,
id: 'last-check-in',
disableFilters: true,
Filter: () => null,
canHide: false,
sortType: 'string',
},
] as const;
function LastCheckinDateCell({
value,
}: CellProps<WaitingRoomEnvironment, number>) {
if (!value) {
return '-';
}
return moment(value * 1000).fromNow();
}

View File

@ -10,6 +10,8 @@ interface TableFiltersStore {
setEdgeGroups(value: number[]): void;
tags: number[];
setTags(value: number[]): void;
checkIn: number;
setCheckIn(value: number): void;
}
export const useFilterStore = createStore<TableFiltersStore>()(
@ -27,6 +29,10 @@ export const useFilterStore = createStore<TableFiltersStore>()(
setTags(tags: number[]) {
set({ tags });
},
checkIn: 0,
setCheckIn(checkIn: number) {
set({ checkIn });
},
}),
{
name: keyBuilder('edge-devices-meta-filters'),

View File

@ -30,6 +30,7 @@ export function useEnvironments() {
tagIds: filterStore.tags.length ? filterStore.tags : undefined,
groupIds: filterStore.groups.length ? filterStore.groups : undefined,
endpointIds: filterByEnvironmentsIds,
edgeCheckInPassedSeconds: filterStore.checkIn,
});
const groupsQuery = useGroups({

View File

@ -29,6 +29,7 @@ export interface EnvironmentsQueryParams {
name?: string;
agentVersions?: string[];
updateInformation?: boolean;
edgeCheckInPassedSeconds?: number;
}
export interface GetEnvironmentsOptions {