diff --git a/app/docker/filters/filters.js b/app/docker/filters/filters.js index e956dc9ff..7c037dd26 100644 --- a/app/docker/filters/filters.js +++ b/app/docker/filters/filters.js @@ -1,5 +1,5 @@ import _ from 'lodash-es'; -import { hideShaSum, joinCommand, nodeStatusBadge, taskStatusBadge, trimSHA } from './utils'; +import { hideShaSum, joinCommand, nodeStatusBadge, taskStatusBadge, trimSHA, trimVersionTag } from './utils'; function includeString(text, values) { return values.some(function (val) { @@ -184,20 +184,7 @@ angular }) .filter('trimversiontag', function () { 'use strict'; - return function trimversiontag(fullName) { - if (!fullName) { - return fullName; - } - var versionIdx = fullName.lastIndexOf(':'); - if (versionIdx < 0) { - return fullName; - } - var hostIdx = fullName.indexOf('/'); - if (hostIdx > versionIdx) { - return fullName; - } - return fullName.substring(0, versionIdx); - }; + return trimVersionTag; }) .filter('unique', function () { return _.uniqBy; diff --git a/app/docker/filters/utils.ts b/app/docker/filters/utils.ts index 8c3e633de..a7e35cd89 100644 --- a/app/docker/filters/utils.ts +++ b/app/docker/filters/utils.ts @@ -1,6 +1,24 @@ import { NodeStatus, TaskState } from 'docker-types/generated/1.41'; import _ from 'lodash'; +export function trimVersionTag(fullName: string) { + if (!fullName) { + return fullName; + } + + const versionIdx = fullName.lastIndexOf(':'); + if (versionIdx < 0) { + return fullName; + } + + const hostIdx = fullName.indexOf('/'); + if (hostIdx > versionIdx) { + return fullName; + } + + return fullName.substring(0, versionIdx); +} + export function trimSHA(imageName: string) { if (!imageName) { return ''; diff --git a/app/react/components/ImageConfigFieldset/AdvancedForm.tsx b/app/react/components/ImageConfigFieldset/AdvancedForm.tsx index 48aa70da3..53132cde0 100644 --- a/app/react/components/ImageConfigFieldset/AdvancedForm.tsx +++ b/app/react/components/ImageConfigFieldset/AdvancedForm.tsx @@ -30,7 +30,7 @@ export function AdvancedForm({ onChange={(e) => { const { value } = e.target; setFieldValue('image', value); - onChangeImage?.(value); + setTimeout(() => onChangeImage?.(value), 0); }} placeholder="e.g. registry:port/my-image:my-tag" required diff --git a/app/react/components/ImageConfigFieldset/InputSearch.tsx b/app/react/components/ImageConfigFieldset/InputSearch.tsx index 38d1321ab..3c3117d2d 100644 --- a/app/react/components/ImageConfigFieldset/InputSearch.tsx +++ b/app/react/components/ImageConfigFieldset/InputSearch.tsx @@ -1,52 +1,39 @@ +import { useMemo } from 'react'; + import { AutomationTestingProps } from '@/types'; +import { AutocompleteSelect } from '@@/form-components/AutocompleteSelect'; import { Option } from '@@/form-components/PortainerSelect'; -import { Select } from '@@/form-components/ReactSelect'; export function InputSearch({ value, onChange, options, placeholder, - 'data-cy': dataCy, inputId, }: { value: string; onChange: (value: string) => void; options: Option[]; placeholder?: string; - inputId?: string; + inputId: string; } & AutomationTestingProps) { - const selectValue = options.find((option) => option.value === value) || { - value: '', - label: value, - }; + const searchResults = useMemo(() => { + if (!value) { + return []; + } + return options.filter((option) => + option.value.toLowerCase().includes(value.toLowerCase()) + ); + }, [options, value]); return ( - )} diff --git a/app/react/portainer/gitops/ComposePathField/PathSelector.tsx b/app/react/portainer/gitops/ComposePathField/PathSelector.tsx index 76544e46b..21e95b153 100644 --- a/app/react/portainer/gitops/ComposePathField/PathSelector.tsx +++ b/app/react/portainer/gitops/ComposePathField/PathSelector.tsx @@ -1,22 +1,10 @@ -import { ChangeEvent } from 'react'; -import { - Combobox, - ComboboxInput, - ComboboxList, - ComboboxOption, - ComboboxPopover, -} from '@reach/combobox'; -import '@reach/combobox/styles.css'; -import clsx from 'clsx'; - import { useSearch } from '@/react/portainer/gitops/queries/useSearch'; -import { useDebounce } from '@/react/hooks/useDebounce'; + +import { AutocompleteSelect } from '@@/form-components/AutocompleteSelect'; import { getAuthentication } from '../utils'; import { GitFormModel } from '../types'; -import styles from './PathSelector.module.css'; - export function PathSelector({ value, onChange, @@ -24,6 +12,7 @@ export function PathSelector({ model, dirOnly, readOnly, + inputId, }: { value: string; onChange(value: string): void; @@ -31,62 +20,33 @@ export function PathSelector({ model: GitFormModel; dirOnly?: boolean; readOnly?: boolean; + inputId: string; }) { - const [searchTerm, setSearchTerm] = useDebounce(value, onChange); - const creds = getAuthentication(model); const payload = { repository: model.RepositoryURL, - keyword: searchTerm, + keyword: value, reference: model.RepositoryReferenceName, tlsSkipVerify: model.TLSSkipVerify, dirOnly, ...creds, }; const enabled = Boolean( - model.RepositoryURL && model.RepositoryURLValid && searchTerm + model.RepositoryURL && model.RepositoryURLValid && value ); const { data: searchResults } = useSearch(payload, enabled); return ( - - - {searchResults && searchResults.length > 0 && ( - - - {searchResults.map((result: string, index: number) => ( - - ))} - - - )} - + ({ + value: result, + label: result, + }))} + value={value} + onChange={onChange} + placeholder={placeholder} + readOnly={readOnly} + inputId={inputId} + /> ); - - function handleChange(e: ChangeEvent) { - setSearchTerm(e.target.value); - } - - function onSelect(value: string) { - onChange(value); - } } diff --git a/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx b/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx index e5acbbe65..2787091bf 100644 --- a/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx +++ b/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx @@ -16,14 +16,14 @@ interface Props { value: RelativePathModel; gitModel?: GitFormModel; onChange?: (value: Partial) => void; - readonly?: boolean; + isEditing?: boolean; } export function RelativePathFieldset({ value, gitModel, onChange, - readonly, + isEditing, }: Props) { const innerOnChange = useCallback( (value: Partial) => onChange && onChange(value), @@ -41,7 +41,7 @@ export function RelativePathFieldset({ label="Enable relative path volumes" labelClass="col-sm-3 col-lg-2" tooltip="Enabling this means you can specify relative path volumes in your Compose files, with Portainer pulling the content from your git repository to the environment the stack is deployed to." - disabled={readonly} + disabled={isEditing} checked={value.SupportRelativePath} onChange={(value) => innerOnChange({ SupportRelativePath: value })} /> @@ -68,7 +68,7 @@ export function RelativePathFieldset({ innerOnChange({ FilesystemPath: e.target.value }) @@ -94,7 +94,7 @@ export function RelativePathFieldset({ label="GitOps Edge configurations" labelClass="col-sm-3 col-lg-2" tooltip="By enabling the GitOps Edge Configurations feature, you gain the ability to define relative path volumes in your configuration files. Portainer will then automatically fetch the content from your git repository by matching the folder name or file name with the Portainer Edge ID, and apply it to the environment where the stack is deployed" - disabled={readonly} + disabled={isEditing} checked={!!value.SupportPerDeviceConfigs} onChange={(value) => innerOnChange({ SupportPerDeviceConfigs: value }) @@ -120,6 +120,7 @@ export function RelativePathFieldset({ @@ -174,7 +176,7 @@ export function RelativePathFieldset({ value: 'dir', }, ]} - disabled={readonly} + disabled={isEditing} /> @@ -205,7 +207,7 @@ export function RelativePathFieldset({ value: 'dir', }, ]} - disabled={readonly} + disabled={isEditing} /> diff --git a/app/react/portainer/gitops/index.ts b/app/react/portainer/gitops/index.ts index e99f7c752..80042d4a5 100644 --- a/app/react/portainer/gitops/index.ts +++ b/app/react/portainer/gitops/index.ts @@ -3,29 +3,18 @@ import angular from 'angular'; import { r2a } from '@/react-tools/react2angular'; import { withUIRouter } from '@/react-tools/withUIRouter'; import { withReactQuery } from '@/react-tools/withReactQuery'; -import { PathSelector } from '@/react/portainer/gitops/ComposePathField/PathSelector'; import { RelativePathFieldset } from '@/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset'; export const ngModule = angular .module('portainer.app.react.gitops', []) - .component( - 'pathSelector', - r2a(withUIRouter(withReactQuery(PathSelector)), [ - 'value', - 'onChange', - 'placeholder', - 'model', - 'dirOnly', - 'readOnly', - ]) - ) + .component( 'relativePathFieldset', r2a(withUIRouter(withReactQuery(RelativePathFieldset)), [ 'value', 'gitModel', 'onChange', - 'readonly', + 'isEditing', ]) ); diff --git a/app/react/portainer/registries/queries/useRegistry.ts b/app/react/portainer/registries/queries/useRegistry.ts index df44444ec..779b8a85f 100644 --- a/app/react/portainer/registries/queries/useRegistry.ts +++ b/app/react/portainer/registries/queries/useRegistry.ts @@ -1,6 +1,7 @@ import { useQuery } from 'react-query'; import axios, { parseAxiosError } from '@/portainer/services/axios'; +import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { Registry } from '../types/registry'; @@ -8,20 +9,26 @@ import { buildUrl } from './build-url'; import { queryKeys } from './query-keys'; export function useRegistry(registryId?: Registry['Id']) { + const environmentId = useEnvironmentId(); + return useQuery( registryId ? queryKeys.item(registryId) : [], - () => (registryId ? getRegistry(registryId) : undefined), + () => (registryId ? getRegistry(registryId, environmentId) : undefined), { enabled: !!registryId, } ); } -async function getRegistry(registryId: Registry['Id']) { +async function getRegistry(registryId: Registry['Id'], environmentId: number) { try { - const { data } = await axios.get(buildUrl(registryId)); + const { data } = await axios.get(buildUrl(registryId), { + params: { + endpointId: environmentId, + }, + }); return data; } catch (err) { - throw parseAxiosError(err as Error, 'Unable to retrieve registry'); + throw parseAxiosError(err as Error, 'XXXUnable to retrieve registry'); } }