mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
feat(edge): associate edge env to meta fields [EE-3209] (#8551)
* refactor(edge/groups): load edge groups in selector fix(edge/stacks): remove double groups title * feat(edge): supply meta fields to edge script [EE-5043] * feat(edge): auto assign aeec envs to groups and tags [EE-5043] fix [EE-5043] fix(envs): fix global key test * fix(edge/groups): save group type * refactor(edge/devices): move loading of devices to table * refactor(tags): select paramter for query * feat(edge/devices): show meta fields * refactor(home): simplify filter * feat(edge/devices): filter by meta fields * refactor(edge/devices): break filter and loading hook
This commit is contained in:
parent
03712966e4
commit
70710cfeb7
32 changed files with 554 additions and 263 deletions
|
@ -16,6 +16,9 @@ const edgePropertiesFormInitialValues: ScriptFormValues = {
|
|||
nomadToken: '',
|
||||
authEnabled: true,
|
||||
tlsEnabled: false,
|
||||
edgeGroupsIds: [],
|
||||
group: 0,
|
||||
tagsIds: [],
|
||||
};
|
||||
|
||||
interface Props {
|
||||
|
@ -23,6 +26,7 @@ interface Props {
|
|||
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
||||
isNomadTokenVisible?: boolean;
|
||||
asyncMode?: boolean;
|
||||
showMetaFields?: boolean;
|
||||
}
|
||||
|
||||
export function EdgeScriptForm({
|
||||
|
@ -30,6 +34,7 @@ export function EdgeScriptForm({
|
|||
commands,
|
||||
isNomadTokenVisible,
|
||||
asyncMode,
|
||||
showMetaFields,
|
||||
children,
|
||||
}: PropsWithChildren<Props>) {
|
||||
const showOsSelector = !(commands instanceof Array);
|
||||
|
@ -50,6 +55,7 @@ export function EdgeScriptForm({
|
|||
isNomadTokenVisible && values.platform === 'nomad'
|
||||
}
|
||||
hideIdGetter={edgeInfo.id !== undefined}
|
||||
showMetaFields={showMetaFields}
|
||||
/>
|
||||
<div className="mt-8">
|
||||
{showOsSelector && (
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import { useFormikContext, Field } from 'formik';
|
||||
|
||||
import { GroupField } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/MetadataFieldset/GroupsField';
|
||||
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { Input } from '@@/form-components/Input';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
import { TagSelector } from '@@/TagSelector';
|
||||
|
||||
import { EdgeGroupsSelector } from '../../edge-stacks/components/EdgeGroupsSelector';
|
||||
|
||||
import { NomadTokenField } from './NomadTokenField';
|
||||
import { ScriptFormValues } from './types';
|
||||
|
@ -11,16 +16,36 @@ import { ScriptFormValues } from './types';
|
|||
interface Props {
|
||||
isNomadTokenVisible?: boolean;
|
||||
hideIdGetter?: boolean;
|
||||
showMetaFields?: boolean;
|
||||
}
|
||||
|
||||
export function EdgeScriptSettingsFieldset({
|
||||
isNomadTokenVisible,
|
||||
hideIdGetter,
|
||||
showMetaFields,
|
||||
}: Props) {
|
||||
const { values, setFieldValue } = useFormikContext<ScriptFormValues>();
|
||||
|
||||
return (
|
||||
<>
|
||||
{showMetaFields && (
|
||||
<>
|
||||
<GroupField name="group" />
|
||||
|
||||
<EdgeGroupsSelector
|
||||
value={values.edgeGroupsIds}
|
||||
onChange={(value) => setFieldValue('edgeGroupsIds', value)}
|
||||
isGroupVisible={(group) => !group.Dynamic}
|
||||
horizontal
|
||||
/>
|
||||
|
||||
<TagSelector
|
||||
value={values.tagsIds}
|
||||
onChange={(value) => setFieldValue('tagsIds', value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!hideIdGetter && (
|
||||
<>
|
||||
<FormControl
|
||||
|
|
|
@ -52,14 +52,6 @@ export const commandsTabs: Record<string, CommandTab> = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
function buildDockerEnvVars(envVars: string, defaultVars: string[]) {
|
||||
const vars = defaultVars.concat(
|
||||
envVars.split(',').filter((s) => s.length > 0)
|
||||
);
|
||||
|
||||
return vars.map((s) => `-e ${s}`).join(' \\\n ');
|
||||
}
|
||||
|
||||
export function buildLinuxStandaloneCommand(
|
||||
agentVersion: string,
|
||||
edgeKey: string,
|
||||
|
@ -70,16 +62,16 @@ export function buildLinuxStandaloneCommand(
|
|||
) {
|
||||
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||
|
||||
const env = buildDockerEnvVars(
|
||||
envVars,
|
||||
buildDefaultEnvVars(
|
||||
const env = buildDockerEnvVars(envVars, [
|
||||
...buildDefaultDockerEnvVars(
|
||||
edgeKey,
|
||||
allowSelfSignedCertificates,
|
||||
!edgeIdGenerator ? edgeId : undefined,
|
||||
agentSecret,
|
||||
useAsyncMode
|
||||
)
|
||||
);
|
||||
),
|
||||
...metaEnvVars(properties),
|
||||
]);
|
||||
|
||||
return `${
|
||||
edgeIdGenerator ? `PORTAINER_EDGE_ID=$(${edgeIdGenerator}) \n\n` : ''
|
||||
|
@ -106,16 +98,16 @@ export function buildWindowsStandaloneCommand(
|
|||
) {
|
||||
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||
|
||||
const env = buildDockerEnvVars(
|
||||
envVars,
|
||||
buildDefaultEnvVars(
|
||||
const env = buildDockerEnvVars(envVars, [
|
||||
...buildDefaultDockerEnvVars(
|
||||
edgeKey,
|
||||
allowSelfSignedCertificates,
|
||||
edgeIdGenerator ? '$Env:PORTAINER_EDGE_ID' : edgeId,
|
||||
agentSecret,
|
||||
useAsyncMode
|
||||
)
|
||||
);
|
||||
),
|
||||
...metaEnvVars(properties),
|
||||
]);
|
||||
|
||||
return `${
|
||||
edgeIdGenerator
|
||||
|
@ -144,7 +136,7 @@ export function buildLinuxSwarmCommand(
|
|||
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||
|
||||
const env = buildDockerEnvVars(envVars, [
|
||||
...buildDefaultEnvVars(
|
||||
...buildDefaultDockerEnvVars(
|
||||
edgeKey,
|
||||
allowSelfSignedCertificates,
|
||||
!edgeIdGenerator ? edgeId : undefined,
|
||||
|
@ -152,6 +144,7 @@ export function buildLinuxSwarmCommand(
|
|||
useAsyncMode
|
||||
),
|
||||
'AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent',
|
||||
...metaEnvVars(properties),
|
||||
]);
|
||||
|
||||
return `${
|
||||
|
@ -186,7 +179,7 @@ export function buildWindowsSwarmCommand(
|
|||
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||
|
||||
const env = buildDockerEnvVars(envVars, [
|
||||
...buildDefaultEnvVars(
|
||||
...buildDefaultDockerEnvVars(
|
||||
edgeKey,
|
||||
allowSelfSignedCertificates,
|
||||
edgeIdGenerator ? '$Env:PORTAINER_EDGE_ID' : edgeId,
|
||||
|
@ -194,6 +187,7 @@ export function buildWindowsSwarmCommand(
|
|||
useAsyncMode
|
||||
),
|
||||
'AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent',
|
||||
...metaEnvVars(properties),
|
||||
]);
|
||||
|
||||
return `${
|
||||
|
@ -229,17 +223,18 @@ export function buildLinuxKubernetesCommand(
|
|||
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||
|
||||
const agentShortVersion = getAgentShortVersion(agentVersion);
|
||||
let envVarsTrimmed = envVars.trim();
|
||||
if (useAsyncMode) {
|
||||
envVarsTrimmed += `EDGE_ASYNC=1`;
|
||||
}
|
||||
const allEnvVars = buildEnvVars(
|
||||
envVars,
|
||||
_.compact([useAsyncMode && 'EDGE_ASYNC=1', ...metaEnvVars(properties)])
|
||||
);
|
||||
|
||||
const idEnvVar = edgeIdGenerator
|
||||
? `PORTAINER_EDGE_ID=$(${edgeIdGenerator}) \n\n`
|
||||
: '';
|
||||
const edgeIdVar = !edgeIdGenerator && edgeId ? edgeId : '$PORTAINER_EDGE_ID';
|
||||
const selfSigned = allowSelfSignedCertificates ? '1' : '0';
|
||||
|
||||
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-setup.sh | bash -s -- "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${agentSecret}" "${envVarsTrimmed}"`;
|
||||
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-setup.sh | bash -s -- "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${agentSecret}" "${allEnvVars}"`;
|
||||
}
|
||||
|
||||
export function buildLinuxNomadCommand(
|
||||
|
@ -259,10 +254,11 @@ export function buildLinuxNomadCommand(
|
|||
} = properties;
|
||||
|
||||
const agentShortVersion = getAgentShortVersion(agentVersion);
|
||||
let envVarsTrimmed = envVars.trim();
|
||||
if (useAsyncMode) {
|
||||
envVarsTrimmed += `EDGE_ASYNC=1`;
|
||||
}
|
||||
|
||||
const allEnvVars = buildEnvVars(
|
||||
envVars,
|
||||
_.compact([useAsyncMode && 'EDGE_ASYNC=1', ...metaEnvVars(properties)])
|
||||
);
|
||||
|
||||
const selfSigned = allowSelfSignedCertificates ? '1' : '0';
|
||||
const idEnvVar = edgeIdGenerator
|
||||
|
@ -270,10 +266,16 @@ export function buildLinuxNomadCommand(
|
|||
: '';
|
||||
const edgeIdVar = !edgeIdGenerator && edgeId ? edgeId : '$PORTAINER_EDGE_ID';
|
||||
|
||||
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-nomad-setup.sh | bash -s -- "${nomadToken}" "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${envVarsTrimmed}" "${agentSecret}" "${tlsEnabled}"`;
|
||||
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-nomad-setup.sh | bash -s -- "${nomadToken}" "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${allEnvVars}" "${agentSecret}" "${tlsEnabled}"`;
|
||||
}
|
||||
|
||||
function buildDefaultEnvVars(
|
||||
function buildDockerEnvVars(envVars: string, moreVars: string[]) {
|
||||
const vars = moreVars.concat(envVars.split(',').filter((s) => s.length > 0));
|
||||
|
||||
return vars.map((s) => `-e ${s}`).join(' \\\n ');
|
||||
}
|
||||
|
||||
function buildDefaultDockerEnvVars(
|
||||
edgeKey: string,
|
||||
allowSelfSignedCerts: boolean,
|
||||
edgeId = '$PORTAINER_EDGE_ID',
|
||||
|
@ -289,3 +291,22 @@ function buildDefaultEnvVars(
|
|||
useAsyncMode ? 'EDGE_ASYNC=1' : '',
|
||||
]);
|
||||
}
|
||||
|
||||
const ENV_VAR_SEPARATOR = ',';
|
||||
const VAR_LIST_SEPARATOR = ':';
|
||||
function buildEnvVars(envVars: string, moreVars: string[]) {
|
||||
return _.compact([envVars.trim(), ...moreVars]).join(ENV_VAR_SEPARATOR);
|
||||
}
|
||||
|
||||
function metaEnvVars({
|
||||
edgeGroupsIds,
|
||||
group,
|
||||
tagsIds,
|
||||
}: Pick<ScriptFormValues, 'edgeGroupsIds' | 'tagsIds' | 'group'>) {
|
||||
return _.compact([
|
||||
edgeGroupsIds.length &&
|
||||
`EDGE_GROUPS=${edgeGroupsIds.join(VAR_LIST_SEPARATOR)}`,
|
||||
group && `PORTAINER_GROUP=${group}`,
|
||||
tagsIds.length && `PORTAINER_TAGS=${tagsIds.join(VAR_LIST_SEPARATOR)}`,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import { TagId } from '@/portainer/tags/types';
|
||||
import { EnvironmentGroupId } from '@/react/portainer/environments/environment-groups/types';
|
||||
|
||||
import { EdgeGroup } from '../../edge-groups/types';
|
||||
|
||||
export type Platform = 'standalone' | 'swarm' | 'k8s' | 'nomad';
|
||||
export type OS = 'win' | 'linux';
|
||||
|
||||
|
@ -13,6 +18,10 @@ export interface ScriptFormValues {
|
|||
platform: Platform;
|
||||
|
||||
edgeIdGenerator?: string;
|
||||
|
||||
group: EnvironmentGroupId;
|
||||
edgeGroupsIds: Array<EdgeGroup['Id']>;
|
||||
tagsIds: Array<TagId>;
|
||||
}
|
||||
|
||||
export interface EdgeInfo {
|
||||
|
|
|
@ -12,27 +12,24 @@ import { useSearchBarState } from '@@/datatables/SearchBar';
|
|||
import { useAssociateDeviceMutation, useLicenseOverused } from '../queries';
|
||||
|
||||
import { columns } from './columns';
|
||||
import { Filter } from './Filter';
|
||||
import { useEnvironments } from './useEnvironments';
|
||||
|
||||
const storageKey = 'edge-devices-waiting-room';
|
||||
|
||||
const settingsStore = createPersistedStore(storageKey, 'Name');
|
||||
|
||||
interface Props {
|
||||
devices: Environment[];
|
||||
isLoading: boolean;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export function Datatable({ devices, isLoading, totalCount }: Props) {
|
||||
export function Datatable() {
|
||||
const associateMutation = useAssociateDeviceMutation();
|
||||
const licenseOverused = useLicenseOverused();
|
||||
const settings = useStore(settingsStore);
|
||||
const [search, setSearch] = useSearchBarState(storageKey);
|
||||
const { data: environments, totalCount, isLoading } = useEnvironments();
|
||||
|
||||
return (
|
||||
<GenericDatatable
|
||||
columns={columns}
|
||||
dataset={devices}
|
||||
dataset={environments}
|
||||
initialPageSize={settings.pageSize}
|
||||
onPageSizeChange={settings.setPageSize}
|
||||
initialSortBy={settings.sortBy}
|
||||
|
@ -62,6 +59,7 @@ export function Datatable({ devices, isLoading, totalCount }: Props) {
|
|||
)}
|
||||
isLoading={isLoading}
|
||||
totalCount={totalCount}
|
||||
description={<Filter />}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { HomepageFilter } from '@/react/portainer/HomeView/EnvironmentList/HomepageFilter';
|
||||
import { useGroups } from '@/react/portainer/environments/environment-groups/queries';
|
||||
import { useEdgeGroups } from '@/react/edge/edge-groups/queries/useEdgeGroups';
|
||||
import { useTags } from '@/portainer/tags/queries';
|
||||
|
||||
import { useFilterStore } from './filter-store';
|
||||
|
||||
export function Filter() {
|
||||
const edgeGroupsQuery = useEdgeGroups();
|
||||
const groupsQuery = useGroups();
|
||||
const tagsQuery = useTags();
|
||||
|
||||
const filterStore = useFilterStore();
|
||||
|
||||
if (!edgeGroupsQuery.data || !groupsQuery.data || !tagsQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full gap-5 [&>*]:w-1/5">
|
||||
<HomepageFilter
|
||||
onChange={(f) => filterStore.setEdgeGroups(f)}
|
||||
placeHolder="Edge groups"
|
||||
value={filterStore.edgeGroups}
|
||||
filterOptions={edgeGroupsQuery.data.map((g) => ({
|
||||
label: g.Name,
|
||||
value: g.Id,
|
||||
}))}
|
||||
/>
|
||||
<HomepageFilter
|
||||
onChange={(f) => filterStore.setGroups(f)}
|
||||
placeHolder="Group"
|
||||
value={filterStore.groups}
|
||||
filterOptions={groupsQuery.data.map((g) => ({
|
||||
label: g.Name,
|
||||
value: g.Id,
|
||||
}))}
|
||||
/>
|
||||
<HomepageFilter
|
||||
onChange={(f) => filterStore.setTags(f)}
|
||||
placeHolder="Tags"
|
||||
value={filterStore.tags}
|
||||
filterOptions={tagsQuery.data.map((g) => ({
|
||||
label: g.Name,
|
||||
value: g.ID,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { Column } from 'react-table';
|
||||
import { CellProps, Column } from 'react-table';
|
||||
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import { WaitingRoomEnvironment } from '../types';
|
||||
|
||||
export const columns: readonly Column<Environment>[] = [
|
||||
export const columns: readonly Column<WaitingRoomEnvironment>[] = [
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: (row) => row.Name,
|
||||
|
@ -21,4 +21,35 @@ export const columns: readonly Column<Environment>[] = [
|
|||
canHide: false,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
Header: 'Edge Groups',
|
||||
accessor: (row) => row.EdgeGroups || [],
|
||||
Cell: ({ value }: CellProps<WaitingRoomEnvironment, string[]>) =>
|
||||
value.join(', ') || '-',
|
||||
id: 'edge-groups',
|
||||
disableFilters: true,
|
||||
Filter: () => null,
|
||||
canHide: false,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
Header: 'Group',
|
||||
accessor: (row) => row.Group || '-',
|
||||
id: 'group',
|
||||
disableFilters: true,
|
||||
Filter: () => null,
|
||||
canHide: false,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
Header: 'Tags',
|
||||
accessor: (row) => row.Tags || [],
|
||||
Cell: ({ value }: CellProps<WaitingRoomEnvironment, string[]>) =>
|
||||
value.join(', ') || '-',
|
||||
id: 'tags',
|
||||
disableFilters: true,
|
||||
Filter: () => null,
|
||||
canHide: false,
|
||||
sortType: 'string',
|
||||
},
|
||||
] as const;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import createStore from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { keyBuilder } from '@/react/hooks/useLocalStorage';
|
||||
|
||||
interface TableFiltersStore {
|
||||
groups: number[];
|
||||
setGroups(value: number[]): void;
|
||||
edgeGroups: number[];
|
||||
setEdgeGroups(value: number[]): void;
|
||||
tags: number[];
|
||||
setTags(value: number[]): void;
|
||||
}
|
||||
|
||||
export const useFilterStore = createStore<TableFiltersStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
edgeGroups: [],
|
||||
setEdgeGroups(edgeGroups: number[]) {
|
||||
set({ edgeGroups });
|
||||
},
|
||||
groups: [],
|
||||
setGroups(groups: number[]) {
|
||||
set({ groups });
|
||||
},
|
||||
tags: [],
|
||||
setTags(tags: number[]) {
|
||||
set({ tags });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: keyBuilder('edge-devices-meta-filters'),
|
||||
}
|
||||
)
|
||||
);
|
|
@ -0,0 +1,74 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { useTags } from '@/portainer/tags/queries';
|
||||
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 { WaitingRoomEnvironment } from '../types';
|
||||
|
||||
import { useFilterStore } from './filter-store';
|
||||
|
||||
export function useEnvironments() {
|
||||
const filterStore = useFilterStore();
|
||||
const edgeGroupsQuery = useEdgeGroups();
|
||||
|
||||
const filterByEnvironmentsIds = filterStore.edgeGroups.length
|
||||
? _.compact(
|
||||
filterStore.edgeGroups.flatMap(
|
||||
(groupId) =>
|
||||
edgeGroupsQuery.data?.find((g) => g.Id === groupId)?.Endpoints
|
||||
)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const environmentsQuery = useEnvironmentList({
|
||||
edgeDeviceUntrusted: true,
|
||||
excludeSnapshots: true,
|
||||
types: EdgeTypes,
|
||||
tagIds: filterStore.tags.length ? filterStore.tags : undefined,
|
||||
groupIds: filterStore.groups.length ? filterStore.groups : undefined,
|
||||
endpointIds: filterByEnvironmentsIds,
|
||||
});
|
||||
|
||||
const groupsQuery = useGroups({
|
||||
select: (groups) =>
|
||||
Object.fromEntries(groups.map((g) => [g.Id, g.Name] as const)),
|
||||
});
|
||||
const environmentEdgeGroupsQuery = useEdgeGroups({
|
||||
select: (groups) =>
|
||||
_.groupBy(
|
||||
groups.flatMap((group) => {
|
||||
const envs = group.Endpoints;
|
||||
return envs.map((id) => ({ id, group: group.Name }));
|
||||
}),
|
||||
(env) => env.id
|
||||
),
|
||||
});
|
||||
const tagsQuery = useTags({
|
||||
select: (tags) =>
|
||||
Object.fromEntries(tags.map((tag) => [tag.ID, tag.Name] as const)),
|
||||
});
|
||||
|
||||
const envs: Array<WaitingRoomEnvironment> =
|
||||
environmentsQuery.environments.map((env) => ({
|
||||
...env,
|
||||
Group: groupsQuery.data?.[env.GroupId] || '',
|
||||
EdgeGroups:
|
||||
environmentEdgeGroupsQuery.data?.[env.Id]?.map((env) => env.group) ||
|
||||
[],
|
||||
Tags:
|
||||
_.compact(env.TagIds?.map((tagId) => tagsQuery.data?.[tagId])) || [],
|
||||
}));
|
||||
|
||||
return {
|
||||
data: envs,
|
||||
isLoading:
|
||||
environmentsQuery.isLoading ||
|
||||
groupsQuery.isLoading ||
|
||||
environmentEdgeGroupsQuery.isLoading ||
|
||||
tagsQuery.isLoading,
|
||||
totalCount: environmentsQuery.totalCount,
|
||||
};
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
import { useEnvironmentList } from '@/react/portainer/environments/queries/useEnvironmentList';
|
||||
import { EdgeTypes } from '@/react/portainer/environments/types';
|
||||
import { withLimitToBE } from '@/react/hooks/useLimitToBE';
|
||||
|
||||
import { InformationPanel } from '@@/InformationPanel';
|
||||
|
@ -11,12 +9,6 @@ import { Datatable } from './Datatable';
|
|||
export default withLimitToBE(WaitingRoomView);
|
||||
|
||||
function WaitingRoomView() {
|
||||
const { environments, isLoading, totalCount } = useEnvironmentList({
|
||||
edgeDeviceUntrusted: true,
|
||||
excludeSnapshots: true,
|
||||
types: EdgeTypes,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
|
@ -35,11 +27,7 @@ function WaitingRoomView() {
|
|||
</TextTip>
|
||||
</InformationPanel>
|
||||
|
||||
<Datatable
|
||||
devices={environments}
|
||||
totalCount={totalCount}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<Datatable />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
7
app/react/edge/edge-devices/WaitingRoomView/types.ts
Normal file
7
app/react/edge/edge-devices/WaitingRoomView/types.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { Environment } from '@/react/portainer/environments/types';
|
||||
|
||||
export type WaitingRoomEnvironment = Environment & {
|
||||
EdgeGroups: string[];
|
||||
Tags: string[];
|
||||
Group: string;
|
||||
};
|
|
@ -1,22 +1,29 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { EnvironmentType } from '@/react/portainer/environments/types';
|
||||
|
||||
import { EdgeGroup } from '../types';
|
||||
|
||||
interface EdgeGroupListItemResponse extends EdgeGroup {
|
||||
EndpointTypes: Array<EnvironmentType>;
|
||||
}
|
||||
|
||||
async function getEdgeGroups() {
|
||||
try {
|
||||
const { data } = await axios.get<EdgeGroup[]>('/edge_groups');
|
||||
const { data } = await axios.get<EdgeGroupListItemResponse[]>(
|
||||
'/edge_groups'
|
||||
);
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw parseAxiosError(err as Error, 'Failed fetching edge groups');
|
||||
}
|
||||
}
|
||||
|
||||
export function useEdgeGroups<T = EdgeGroup[]>({
|
||||
export function useEdgeGroups<T = EdgeGroupListItemResponse[]>({
|
||||
select,
|
||||
}: {
|
||||
select?: (groups: EdgeGroup[]) => T;
|
||||
select?: (groups: EdgeGroupListItemResponse[]) => T;
|
||||
} = {}) {
|
||||
return useQuery(['edge', 'groups'], getEdgeGroups, { select });
|
||||
}
|
||||
|
|
|
@ -3,21 +3,78 @@ import _ from 'lodash';
|
|||
import { EdgeGroup } from '@/react/edge/edge-groups/types';
|
||||
|
||||
import { Select } from '@@/form-components/ReactSelect';
|
||||
import { FormSection } from '@@/form-components/FormSection';
|
||||
import { FormError } from '@@/form-components/FormError';
|
||||
import { Link } from '@@/Link';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
|
||||
import { useEdgeGroups } from '../../edge-groups/queries/useEdgeGroups';
|
||||
|
||||
type SingleValue = EdgeGroup['Id'];
|
||||
|
||||
interface Props {
|
||||
items: EdgeGroup[];
|
||||
value: SingleValue[];
|
||||
onChange: (value: SingleValue[]) => void;
|
||||
error?: string | string[];
|
||||
horizontal?: boolean;
|
||||
isGroupVisible?(group: EdgeGroup): boolean;
|
||||
}
|
||||
|
||||
export function EdgeGroupsSelector({ items, value, onChange }: Props) {
|
||||
export function EdgeGroupsSelector({
|
||||
value,
|
||||
onChange,
|
||||
error,
|
||||
horizontal,
|
||||
isGroupVisible = () => true,
|
||||
}: Props) {
|
||||
const selector = (
|
||||
<InnerSelector
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isGroupVisible={isGroupVisible}
|
||||
/>
|
||||
);
|
||||
|
||||
return horizontal ? (
|
||||
<FormControl errors={error} label="Edge Groups">
|
||||
{selector}
|
||||
</FormControl>
|
||||
) : (
|
||||
<FormSection title="Edge Groups">
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">{selector} </div>
|
||||
{error && (
|
||||
<div className="col-sm-12">
|
||||
<FormError>{error}</FormError>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</FormSection>
|
||||
);
|
||||
}
|
||||
|
||||
function InnerSelector({
|
||||
value,
|
||||
onChange,
|
||||
isGroupVisible,
|
||||
}: {
|
||||
isGroupVisible(group: EdgeGroup): boolean;
|
||||
value: SingleValue[];
|
||||
onChange: (value: SingleValue[]) => void;
|
||||
}) {
|
||||
const edgeGroupsQuery = useEdgeGroups();
|
||||
|
||||
if (!edgeGroupsQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items = edgeGroupsQuery.data.filter(isGroupVisible);
|
||||
|
||||
const valueGroups = _.compact(
|
||||
value.map((id) => items.find((item) => item.Id === id))
|
||||
);
|
||||
|
||||
return (
|
||||
return items.length ? (
|
||||
<Select
|
||||
aria-label="Edge groups"
|
||||
options={items}
|
||||
|
@ -31,5 +88,10 @@ export function EdgeGroupsSelector({ items, value, onChange }: Props) {
|
|||
placeholder="Select one or multiple group(s)"
|
||||
closeMenuOnSelect={false}
|
||||
/>
|
||||
) : (
|
||||
<div className="small text-muted">
|
||||
No Edge groups are available. Head over to the{' '}
|
||||
<Link to="edge.groups">Edge groups view</Link> to create one.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue