mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 13:29:41 +02:00
feat(docker/containers): limit items on volume selector [EE-7077] (#11845)
Some checks failed
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
ci / build_images (map[arch:arm platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:s390x platform:linux version:]) (push) Has been cancelled
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:linux]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
Test / test-server (map[arch:arm64 platform:linux]) (push) Has been cancelled
ci / build_manifests (push) Has been cancelled
Some checks failed
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
ci / build_images (map[arch:arm platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:s390x platform:linux version:]) (push) Has been cancelled
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:linux]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
Test / test-server (map[arch:arm64 platform:linux]) (push) Has been cancelled
ci / build_manifests (push) Has been cancelled
This commit is contained in:
parent
d7b412eccc
commit
50fd7c6286
3 changed files with 109 additions and 9 deletions
|
@ -96,7 +96,7 @@
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group .portainer-selector-root .portainer-selector__control:not(:last-child) {
|
.input-group .portainer-selector-root .portainer-selector__control:not(:last-child, .portainer-selector__control--menu-is-open) {
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import ReactSelectCreatable, {
|
import ReactSelectCreatable, {
|
||||||
CreatableProps as ReactSelectCreatableProps,
|
CreatableProps as ReactSelectCreatableProps,
|
||||||
} from 'react-select/creatable';
|
} from 'react-select/creatable';
|
||||||
|
import ReactSelectAsync, {
|
||||||
|
AsyncProps as ReactSelectAsyncProps,
|
||||||
|
} from 'react-select/async';
|
||||||
import ReactSelect, {
|
import ReactSelect, {
|
||||||
GroupBase,
|
GroupBase,
|
||||||
|
OptionsOrGroups,
|
||||||
Props as ReactSelectProps,
|
Props as ReactSelectProps,
|
||||||
} from 'react-select';
|
} from 'react-select';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { RefAttributes } from 'react';
|
import { RefAttributes, useMemo } from 'react';
|
||||||
import ReactSelectType from 'react-select/dist/declarations/src/Select';
|
import ReactSelectType from 'react-select/dist/declarations/src/Select';
|
||||||
|
|
||||||
import './ReactSelect.css';
|
import './ReactSelect.css';
|
||||||
|
@ -56,9 +60,24 @@ export function Select<
|
||||||
className,
|
className,
|
||||||
isCreatable = false,
|
isCreatable = false,
|
||||||
size = 'md',
|
size = 'md',
|
||||||
|
|
||||||
...props
|
...props
|
||||||
}: Props<Option, IsMulti, Group> & AutomationTestingProps) {
|
}: Props<Option, IsMulti, Group> &
|
||||||
|
AutomationTestingProps & {
|
||||||
|
isItemVisible?: (item: Option, search: string) => boolean;
|
||||||
|
}) {
|
||||||
const Component = isCreatable ? ReactSelectCreatable : ReactSelect;
|
const Component = isCreatable ? ReactSelectCreatable : ReactSelect;
|
||||||
|
const { options } = props;
|
||||||
|
|
||||||
|
if ((options?.length || 0) > 1000) {
|
||||||
|
return (
|
||||||
|
<TooManyResultsSelector
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...props}
|
||||||
|
size={size}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
|
@ -84,3 +103,86 @@ export function Creatable<
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Async<
|
||||||
|
Option = DefaultOption,
|
||||||
|
IsMulti extends boolean = false,
|
||||||
|
Group extends GroupBase<Option> = GroupBase<Option>,
|
||||||
|
>({
|
||||||
|
className,
|
||||||
|
size,
|
||||||
|
...props
|
||||||
|
}: ReactSelectAsyncProps<Option, IsMulti, Group> & { size?: 'sm' | 'md' }) {
|
||||||
|
return (
|
||||||
|
<ReactSelectAsync
|
||||||
|
className={clsx(className, 'portainer-selector-root', size)}
|
||||||
|
classNamePrefix="portainer-selector"
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TooManyResultsSelector<
|
||||||
|
Option = DefaultOption,
|
||||||
|
IsMulti extends boolean = false,
|
||||||
|
Group extends GroupBase<Option> = GroupBase<Option>,
|
||||||
|
>({
|
||||||
|
options,
|
||||||
|
isLoading,
|
||||||
|
getOptionValue,
|
||||||
|
isItemVisible = (item, search) =>
|
||||||
|
!!getOptionValue?.(item).toLowerCase().includes(search.toLowerCase()),
|
||||||
|
...props
|
||||||
|
}: RegularProps<Option, IsMulti, Group> & {
|
||||||
|
isItemVisible?: (item: Option, search: string) => boolean;
|
||||||
|
}) {
|
||||||
|
const defaultOptions = useMemo(() => options?.slice(0, 100), [options]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Async
|
||||||
|
isLoading={isLoading}
|
||||||
|
getOptionValue={getOptionValue}
|
||||||
|
loadOptions={(search: string) =>
|
||||||
|
filterOptions<Option, Group>(options, isItemVisible, search)
|
||||||
|
}
|
||||||
|
defaultOptions={defaultOptions}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterOptions<
|
||||||
|
Option = DefaultOption,
|
||||||
|
Group extends GroupBase<Option> = GroupBase<Option>,
|
||||||
|
>(
|
||||||
|
options: OptionsOrGroups<Option, Group> | undefined,
|
||||||
|
isItemVisible: (item: Option, search: string) => boolean,
|
||||||
|
search: string
|
||||||
|
): Promise<OptionsOrGroups<Option, Group> | undefined> {
|
||||||
|
return Promise.resolve<OptionsOrGroups<Option, Group> | undefined>(
|
||||||
|
options
|
||||||
|
?.filter((item) =>
|
||||||
|
isGroup(item)
|
||||||
|
? item.options.some((ni) => isItemVisible(ni, search))
|
||||||
|
: isItemVisible(item, search)
|
||||||
|
)
|
||||||
|
.slice(0, 100)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGroup<
|
||||||
|
Option = DefaultOption,
|
||||||
|
Group extends GroupBase<Option> = GroupBase<Option>,
|
||||||
|
>(option: Option | Group): option is Group {
|
||||||
|
if (!option) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof option !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'options' in option;
|
||||||
|
}
|
||||||
|
|
|
@ -22,16 +22,13 @@ export function VolumeSelector({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!volumesQuery.data) {
|
const initialVolumes = volumesQuery.data || [];
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const volumes = allowAuto
|
const volumes = allowAuto
|
||||||
? [...volumesQuery.data, { Name: 'auto', Driver: '' }]
|
? [...initialVolumes, { Name: 'auto', Driver: '' }]
|
||||||
: volumesQuery.data;
|
: initialVolumes;
|
||||||
|
|
||||||
const selectedValue = volumes.find((vol) => vol.Name === value);
|
const selectedValue = volumes.find((vol) => vol.Name === value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
placeholder="Select a volume"
|
placeholder="Select a volume"
|
||||||
|
@ -47,6 +44,7 @@ export function VolumeSelector({
|
||||||
onChange={(vol) => onChange(vol?.Name)}
|
onChange={(vol) => onChange(vol?.Name)}
|
||||||
inputId={inputId}
|
inputId={inputId}
|
||||||
data-cy="docker-containers-volume-selector"
|
data-cy="docker-containers-volume-selector"
|
||||||
|
size="sm"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue