mirror of https://github.com/portainer/portainer
feat(edge): sort waiting room table [EE-6259] (#10577)
parent
32d8dc311b
commit
25741e8c4c
|
@ -2,7 +2,6 @@ package endpoints
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
@ -30,7 +29,7 @@ const (
|
|||
// @produce json
|
||||
// @param start query int false "Start searching from"
|
||||
// @param limit query int false "Limit results to this value"
|
||||
// @param sort query int false "Sort results by this value"
|
||||
// @param sort query sortKey false "Sort results by this value" Enum("Name", "Group", "Status", "LastCheckIn", "EdgeID")
|
||||
// @param order query int false "Order sorted results by desc/asc" Enum("asc", "desc")
|
||||
// @param search query string false "Search query"
|
||||
// @param groupIds query []int false "List environments(endpoints) of these groups"
|
||||
|
@ -98,7 +97,7 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
|
|||
return httperror.InternalServerError("Unable to filter endpoints", err)
|
||||
}
|
||||
|
||||
sortEndpointsByField(filteredEndpoints, endpointGroups, sortField, sortOrder == "desc")
|
||||
sortEnvironmentsByField(filteredEndpoints, endpointGroups, getSortKey(sortField), sortOrder == "desc")
|
||||
|
||||
filteredEndpointCount := len(filteredEndpoints)
|
||||
|
||||
|
@ -147,46 +146,6 @@ func paginateEndpoints(endpoints []portainer.Endpoint, start, limit int) []porta
|
|||
return endpoints[start:end]
|
||||
}
|
||||
|
||||
func sortEndpointsByField(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, sortField string, isSortDesc bool) {
|
||||
|
||||
switch sortField {
|
||||
case "Name":
|
||||
if isSortDesc {
|
||||
sort.Stable(sort.Reverse(EndpointsByName(endpoints)))
|
||||
} else {
|
||||
sort.Stable(EndpointsByName(endpoints))
|
||||
}
|
||||
|
||||
case "Group":
|
||||
endpointGroupNames := make(map[portainer.EndpointGroupID]string, 0)
|
||||
for _, group := range endpointGroups {
|
||||
endpointGroupNames[group.ID] = group.Name
|
||||
}
|
||||
|
||||
endpointsByGroup := EndpointsByGroup{
|
||||
endpointGroupNames: endpointGroupNames,
|
||||
endpoints: endpoints,
|
||||
}
|
||||
|
||||
if isSortDesc {
|
||||
sort.Stable(sort.Reverse(endpointsByGroup))
|
||||
} else {
|
||||
sort.Stable(endpointsByGroup)
|
||||
}
|
||||
|
||||
case "Status":
|
||||
if isSortDesc {
|
||||
sort.Slice(endpoints, func(i, j int) bool {
|
||||
return endpoints[i].Status > endpoints[j].Status
|
||||
})
|
||||
} else {
|
||||
sort.Slice(endpoints, func(i, j int) bool {
|
||||
return endpoints[i].Status < endpoints[j].Status
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getEndpointGroup(groupID portainer.EndpointGroupID, groups []portainer.EndpointGroup) portainer.EndpointGroup {
|
||||
var endpointGroup portainer.EndpointGroup
|
||||
for _, group := range groups {
|
||||
|
|
|
@ -1,46 +1,94 @@
|
|||
package endpoints
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"slices"
|
||||
|
||||
"github.com/fvbommel/sortorder"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type EndpointsByName []portainer.Endpoint
|
||||
type comp[T any] func(a, b T) int
|
||||
|
||||
func (e EndpointsByName) Len() int {
|
||||
return len(e)
|
||||
func stringComp(a, b string) int {
|
||||
if sortorder.NaturalLess(a, b) {
|
||||
return -1
|
||||
} else if sortorder.NaturalLess(b, a) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (e EndpointsByName) Swap(i, j int) {
|
||||
e[i], e[j] = e[j], e[i]
|
||||
}
|
||||
|
||||
func (e EndpointsByName) Less(i, j int) bool {
|
||||
return sortorder.NaturalLess(strings.ToLower(e[i].Name), strings.ToLower(e[j].Name))
|
||||
}
|
||||
|
||||
type EndpointsByGroup struct {
|
||||
endpointGroupNames map[portainer.EndpointGroupID]string
|
||||
endpoints []portainer.Endpoint
|
||||
}
|
||||
|
||||
func (e EndpointsByGroup) Len() int {
|
||||
return len(e.endpoints)
|
||||
}
|
||||
|
||||
func (e EndpointsByGroup) Swap(i, j int) {
|
||||
e.endpoints[i], e.endpoints[j] = e.endpoints[j], e.endpoints[i]
|
||||
}
|
||||
|
||||
func (e EndpointsByGroup) Less(i, j int) bool {
|
||||
if e.endpoints[i].GroupID == e.endpoints[j].GroupID {
|
||||
return false
|
||||
func sortEnvironmentsByField(environments []portainer.Endpoint, environmentGroups []portainer.EndpointGroup, sortField sortKey, isSortDesc bool) {
|
||||
if sortField == "" {
|
||||
return
|
||||
}
|
||||
|
||||
groupA := e.endpointGroupNames[e.endpoints[i].GroupID]
|
||||
groupB := e.endpointGroupNames[e.endpoints[j].GroupID]
|
||||
var less comp[portainer.Endpoint]
|
||||
switch sortField {
|
||||
case sortKeyName:
|
||||
less = func(a, b portainer.Endpoint) int {
|
||||
return stringComp(a.Name, b.Name)
|
||||
}
|
||||
|
||||
case sortKeyGroup:
|
||||
environmentGroupNames := make(map[portainer.EndpointGroupID]string, 0)
|
||||
for _, group := range environmentGroups {
|
||||
environmentGroupNames[group.ID] = group.Name
|
||||
}
|
||||
|
||||
// set the "unassigned" group name to be empty string
|
||||
environmentGroupNames[1] = ""
|
||||
|
||||
less = func(a, b portainer.Endpoint) int {
|
||||
aGroup := environmentGroupNames[a.GroupID]
|
||||
bGroup := environmentGroupNames[b.GroupID]
|
||||
|
||||
return stringComp(aGroup, bGroup)
|
||||
}
|
||||
|
||||
case sortKeyStatus:
|
||||
less = func(a, b portainer.Endpoint) int {
|
||||
return int(a.Status - b.Status)
|
||||
}
|
||||
|
||||
case sortKeyLastCheckInDate:
|
||||
less = func(a, b portainer.Endpoint) int {
|
||||
return int(a.LastCheckInDate - b.LastCheckInDate)
|
||||
}
|
||||
case sortKeyEdgeID:
|
||||
less = func(a, b portainer.Endpoint) int {
|
||||
return stringComp(a.EdgeID, b.EdgeID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
slices.SortStableFunc(environments, func(a, b portainer.Endpoint) int {
|
||||
mul := 1
|
||||
if isSortDesc {
|
||||
mul = -1
|
||||
}
|
||||
|
||||
return less(a, b) * mul
|
||||
})
|
||||
|
||||
return sortorder.NaturalLess(strings.ToLower(groupA), strings.ToLower(groupB))
|
||||
}
|
||||
|
||||
type sortKey string
|
||||
|
||||
const (
|
||||
sortKeyName sortKey = "Name"
|
||||
sortKeyGroup sortKey = "Group"
|
||||
sortKeyStatus sortKey = "Status"
|
||||
sortKeyLastCheckInDate sortKey = "LastCheckIn"
|
||||
sortKeyEdgeID sortKey = "EdgeID"
|
||||
)
|
||||
|
||||
func getSortKey(sortField string) sortKey {
|
||||
fieldAsSortKey := sortKey(sortField)
|
||||
if slices.Contains([]sortKey{sortKeyName, sortKeyGroup, sortKeyStatus, sortKeyLastCheckInDate, sortKeyEdgeID}, fieldAsSortKey) {
|
||||
return fieldAsSortKey
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
package endpoints
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/slices"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSortEndpointsByField(t *testing.T) {
|
||||
environments := []portainer.Endpoint{
|
||||
{ID: 0, Name: "Environment 1", GroupID: 1, Status: 1, LastCheckInDate: 3, EdgeID: "edge32"},
|
||||
{ID: 1, Name: "Environment 2", GroupID: 2, Status: 2, LastCheckInDate: 6, EdgeID: "edge57"},
|
||||
{ID: 2, Name: "Environment 3", GroupID: 1, Status: 3, LastCheckInDate: 2, EdgeID: "test87"},
|
||||
{ID: 3, Name: "Environment 4", GroupID: 2, Status: 4, LastCheckInDate: 1, EdgeID: "abc123"},
|
||||
}
|
||||
|
||||
environmentGroups := []portainer.EndpointGroup{
|
||||
{ID: 1, Name: "Group 1"},
|
||||
{ID: 2, Name: "Group 2"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sortField sortKey
|
||||
isSortDesc bool
|
||||
expected []portainer.EndpointID
|
||||
}{
|
||||
{
|
||||
name: "sort without value",
|
||||
sortField: "",
|
||||
expected: []portainer.EndpointID{
|
||||
environments[0].ID,
|
||||
environments[1].ID,
|
||||
environments[2].ID,
|
||||
environments[3].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by name ascending",
|
||||
sortField: "Name",
|
||||
isSortDesc: false,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[0].ID,
|
||||
environments[1].ID,
|
||||
environments[2].ID,
|
||||
environments[3].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by name descending",
|
||||
sortField: "Name",
|
||||
isSortDesc: true,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[3].ID,
|
||||
environments[2].ID,
|
||||
environments[1].ID,
|
||||
environments[0].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by group name ascending",
|
||||
sortField: "Group",
|
||||
isSortDesc: false,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[0].ID,
|
||||
environments[2].ID,
|
||||
environments[1].ID,
|
||||
environments[3].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by group name descending",
|
||||
sortField: "Group",
|
||||
isSortDesc: true,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[1].ID,
|
||||
environments[3].ID,
|
||||
environments[0].ID,
|
||||
environments[2].ID,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "sort by status ascending",
|
||||
sortField: "Status",
|
||||
isSortDesc: false,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[0].ID,
|
||||
environments[1].ID,
|
||||
environments[2].ID,
|
||||
environments[3].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by status descending",
|
||||
sortField: "Status",
|
||||
isSortDesc: true,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[3].ID,
|
||||
environments[2].ID,
|
||||
environments[1].ID,
|
||||
environments[0].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by last check-in ascending",
|
||||
sortField: "LastCheckIn",
|
||||
isSortDesc: false,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[3].ID,
|
||||
environments[2].ID,
|
||||
environments[0].ID,
|
||||
environments[1].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by last check-in descending",
|
||||
sortField: "LastCheckIn",
|
||||
isSortDesc: true,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[1].ID,
|
||||
environments[0].ID,
|
||||
environments[2].ID,
|
||||
environments[3].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by edge ID ascending",
|
||||
sortField: "EdgeID",
|
||||
expected: []portainer.EndpointID{
|
||||
environments[3].ID,
|
||||
environments[0].ID,
|
||||
environments[1].ID,
|
||||
environments[2].ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by edge ID descending",
|
||||
sortField: "EdgeID",
|
||||
isSortDesc: true,
|
||||
expected: []portainer.EndpointID{
|
||||
environments[2].ID,
|
||||
environments[1].ID,
|
||||
environments[0].ID,
|
||||
environments[3].ID,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
sortEnvironmentsByField(environments, environmentGroups, "Name", false) // reset to default sort order
|
||||
|
||||
sortEnvironmentsByField(environments, environmentGroups, tt.sortField, tt.isSortDesc)
|
||||
|
||||
is.Equal(tt.expected, getEndpointIDs(environments))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getEndpointIDs(environments []portainer.Endpoint) []portainer.EndpointID {
|
||||
return slices.Map(environments, func(environment portainer.Endpoint) portainer.EndpointID {
|
||||
return environment.ID
|
||||
})
|
||||
}
|
|
@ -22,6 +22,7 @@ export function Datatable() {
|
|||
} = useEnvironments({
|
||||
pageLimit: tableState.pageSize,
|
||||
search: tableState.search,
|
||||
sortBy: tableState.sortBy,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,27 +8,32 @@ const columnHelper = createColumnHelper<WaitingRoomEnvironment>();
|
|||
export const columns = [
|
||||
columnHelper.accessor('Name', {
|
||||
header: 'Name',
|
||||
id: 'name',
|
||||
id: 'Name',
|
||||
}),
|
||||
columnHelper.accessor('EdgeID', {
|
||||
header: 'Edge ID',
|
||||
id: 'edge-id',
|
||||
id: 'EdgeID',
|
||||
}),
|
||||
columnHelper.accessor((row) => row.EdgeGroups.join(', ') || '-', {
|
||||
columnHelper.accessor((row) => row.EdgeGroups.join(', '), {
|
||||
header: 'Edge Groups',
|
||||
id: 'edge-groups',
|
||||
enableSorting: false,
|
||||
cell: ({ getValue }) => getValue() || '-',
|
||||
}),
|
||||
columnHelper.accessor((row) => row.Group || '-', {
|
||||
columnHelper.accessor((row) => row.Group, {
|
||||
header: 'Group',
|
||||
id: 'group',
|
||||
id: 'Group',
|
||||
cell: ({ getValue }) => getValue() || '-',
|
||||
}),
|
||||
columnHelper.accessor((row) => row.Tags.join(', ') || '-', {
|
||||
columnHelper.accessor((row) => row.Tags.join(', '), {
|
||||
header: 'Tags',
|
||||
id: 'tags',
|
||||
enableSorting: false,
|
||||
cell: ({ getValue }) => getValue() || '-',
|
||||
}),
|
||||
columnHelper.accessor((row) => row.LastCheckInDate, {
|
||||
header: 'Last Check-in',
|
||||
id: 'last-check-in',
|
||||
id: 'LastCheckIn',
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? moment(value * 1000).fromNow() : '-';
|
||||
|
|
|
@ -6,6 +6,10 @@ import { useEdgeGroups } from '@/react/edge/edge-groups/queries/useEdgeGroups';
|
|||
import { useGroups } from '@/react/portainer/environments/environment-groups/queries';
|
||||
import { useEnvironmentList } from '@/react/portainer/environments/queries';
|
||||
import { EdgeTypes } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
Query,
|
||||
getSortType,
|
||||
} from '@/react/portainer/environments/queries/useEnvironmentList';
|
||||
|
||||
import { WaitingRoomEnvironment } from '../types';
|
||||
|
||||
|
@ -14,9 +18,11 @@ import { useFilterStore } from './filter-store';
|
|||
export function useEnvironments({
|
||||
pageLimit = 10,
|
||||
search,
|
||||
sortBy,
|
||||
}: {
|
||||
pageLimit: number;
|
||||
search: string;
|
||||
sortBy: { id: string; desc: boolean } | undefined;
|
||||
}) {
|
||||
const [page, setPage] = useState(0);
|
||||
const filterStore = useFilterStore();
|
||||
|
@ -35,7 +41,7 @@ export function useEnvironments({
|
|||
[edgeGroupsQuery.data, filterStore.edgeGroups]
|
||||
);
|
||||
|
||||
const query = useMemo(
|
||||
const query: Partial<Query> = useMemo(
|
||||
() => ({
|
||||
pageLimit,
|
||||
edgeDeviceUntrusted: true,
|
||||
|
@ -46,6 +52,8 @@ export function useEnvironments({
|
|||
endpointIds: filterByEnvironmentsIds,
|
||||
edgeCheckInPassedSeconds: filterStore.checkIn,
|
||||
search,
|
||||
sort: getSortType(sortBy?.id),
|
||||
order: sortBy?.desc ? 'desc' : 'asc',
|
||||
}),
|
||||
[
|
||||
filterByEnvironmentsIds,
|
||||
|
@ -54,6 +62,7 @@ export function useEnvironments({
|
|||
filterStore.tags,
|
||||
pageLimit,
|
||||
search,
|
||||
sortBy,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
import { EnvironmentGroupId } from '@/react/portainer/environments/environment-groups/types';
|
||||
import {
|
||||
refetchIfAnyOffline,
|
||||
SortType,
|
||||
useEnvironmentList,
|
||||
} from '@/react/portainer/environments/queries/useEnvironmentList';
|
||||
import { useGroups } from '@/react/portainer/environments/environment-groups/queries';
|
||||
|
@ -37,6 +36,7 @@ import { NoEnvironmentsInfoPanel } from './NoEnvironmentsInfoPanel';
|
|||
import { UpdateBadge } from './UpdateBadge';
|
||||
import { EnvironmentListFilters } from './EnvironmentListFilters';
|
||||
import { AMTButton } from './AMTButton/AMTButton';
|
||||
import { ListSortType } from './SortbySelector';
|
||||
|
||||
interface Props {
|
||||
onClickBrowse(environment: Environment): void;
|
||||
|
@ -70,7 +70,7 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
|
|||
[]
|
||||
);
|
||||
const [sortByFilter, setSortByFilter] = useHomePageFilter<
|
||||
SortType | undefined
|
||||
ListSortType | undefined
|
||||
>('sortBy', undefined);
|
||||
const [sortByDescending, setSortByDescending] = useHomePageFilter(
|
||||
'sortOrder',
|
||||
|
|
|
@ -5,13 +5,9 @@ import { useTags } from '@/portainer/tags/queries';
|
|||
import { useAgentVersionsList } from '../../environments/queries/useAgentVersionsList';
|
||||
import { EnvironmentStatus, PlatformType } from '../../environments/types';
|
||||
import { useGroups } from '../../environments/environment-groups/queries';
|
||||
import {
|
||||
SortOptions,
|
||||
SortType,
|
||||
} from '../../environments/queries/useEnvironmentList';
|
||||
|
||||
import { HomepageFilter } from './HomepageFilter';
|
||||
import { SortbySelector } from './SortbySelector';
|
||||
import { ListSortType, SortbySelector } from './SortbySelector';
|
||||
import { ConnectionType } from './types';
|
||||
import styles from './EnvironmentList.module.css';
|
||||
|
||||
|
@ -20,11 +16,6 @@ const status = [
|
|||
{ value: EnvironmentStatus.Down, label: 'Down' },
|
||||
];
|
||||
|
||||
const sortByOptions = SortOptions.map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
}));
|
||||
|
||||
export function EnvironmentListFilters({
|
||||
agentVersions,
|
||||
clearFilter,
|
||||
|
@ -63,8 +54,8 @@ export function EnvironmentListFilters({
|
|||
setAgentVersions: (value: string[]) => void;
|
||||
agentVersions: string[];
|
||||
|
||||
sortByState?: SortType;
|
||||
sortOnChange: (value: SortType) => void;
|
||||
sortByState?: ListSortType;
|
||||
sortOnChange: (value: ListSortType) => void;
|
||||
|
||||
sortOnDescending: () => void;
|
||||
sortByDescending: boolean;
|
||||
|
@ -160,7 +151,6 @@ export function EnvironmentListFilters({
|
|||
|
||||
<div className={styles.filterRight}>
|
||||
<SortbySelector
|
||||
filterOptions={sortByOptions}
|
||||
onChange={sortOnChange}
|
||||
onDescending={sortOnDescending}
|
||||
placeHolder="Sort By"
|
||||
|
|
|
@ -1,24 +1,34 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { Option, PortainerSelect } from '@@/form-components/PortainerSelect';
|
||||
import { PortainerSelect } from '@@/form-components/PortainerSelect';
|
||||
import { TableHeaderSortIcons } from '@@/datatables/TableHeaderSortIcons';
|
||||
|
||||
import { SortType } from '../../environments/queries/useEnvironmentList';
|
||||
import {
|
||||
SortOptions,
|
||||
SortType,
|
||||
} from '../../environments/queries/useEnvironmentList';
|
||||
|
||||
import styles from './SortbySelector.module.css';
|
||||
|
||||
export type ListSortType = Exclude<SortType, 'LastCheckIn' | 'EdgeID'>;
|
||||
|
||||
const sortByOptions = SortOptions.filter(
|
||||
(v): v is ListSortType => !['LastCheckIn', 'EdgeID'].includes(v)
|
||||
).map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
}));
|
||||
|
||||
interface Props {
|
||||
filterOptions: Option<SortType>[];
|
||||
onChange: (value: SortType) => void;
|
||||
onChange: (value: ListSortType) => void;
|
||||
onDescending: () => void;
|
||||
placeHolder: string;
|
||||
sortByDescending: boolean;
|
||||
sortByButton: boolean;
|
||||
value?: SortType;
|
||||
value?: ListSortType;
|
||||
}
|
||||
|
||||
export function SortbySelector({
|
||||
filterOptions,
|
||||
onChange,
|
||||
onDescending,
|
||||
placeHolder,
|
||||
|
@ -31,8 +41,8 @@ export function SortbySelector({
|
|||
<div className="flex items-center justify-end gap-1">
|
||||
<PortainerSelect
|
||||
placeholder={placeHolder}
|
||||
options={filterOptions}
|
||||
onChange={(option: SortType) => onChange(option || '')}
|
||||
options={sortByOptions}
|
||||
onChange={(option: ListSortType) => onChange(option)}
|
||||
isClearable
|
||||
value={value}
|
||||
/>
|
||||
|
|
|
@ -12,12 +12,22 @@ import { environmentQueryKeys } from './query-keys';
|
|||
|
||||
export const ENVIRONMENTS_POLLING_INTERVAL = 30000; // in ms
|
||||
|
||||
export const SortOptions = ['Name', 'Group', 'Status'] as const;
|
||||
export const SortOptions = [
|
||||
'Name',
|
||||
'Group',
|
||||
'Status',
|
||||
'LastCheckIn',
|
||||
'EdgeID',
|
||||
] as const;
|
||||
export type SortType = (typeof SortOptions)[number];
|
||||
export function isSortType(value?: string): value is SortType {
|
||||
return SortOptions.includes(value as SortType);
|
||||
}
|
||||
|
||||
export function getSortType(value?: string): SortType | undefined {
|
||||
return isSortType(value) ? value : undefined;
|
||||
}
|
||||
|
||||
export type Query = EnvironmentsQueryParams & {
|
||||
page?: number;
|
||||
pageLimit?: number;
|
||||
|
|
Loading…
Reference in New Issue