diff --git a/app/portainer/__module.js b/app/portainer/__module.js index b43401dd5..eec3bf318 100644 --- a/app/portainer/__module.js +++ b/app/portainer/__module.js @@ -171,7 +171,7 @@ angular url: '/endpoints', views: { 'content@': { - component: 'endpointsView', + component: 'environmentsListView', }, }, }; diff --git a/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.html b/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.html deleted file mode 100644 index fd9db10cf..000000000 --- a/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.html +++ /dev/null @@ -1,167 +0,0 @@ -
- - -
-
-
- -
- - {{ $ctrl.titleText }} -
- -
- - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- - - - - - - -
- - - - - {{ item.Name }} - - - - {{ item.Type | endpointtypename }} - - - {{ item.URL | stripprotocol }} - - - {{ item.GroupName }} - - - Manage access - -
Loading...
No environment available.
-
- -
-
-
diff --git a/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.js b/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.js deleted file mode 100644 index 807f05192..000000000 --- a/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.js +++ /dev/null @@ -1,13 +0,0 @@ -angular.module('portainer.app').component('endpointsDatatable', { - templateUrl: './endpointsDatatable.html', - controller: 'EndpointsDatatableController', - bindings: { - titleText: '@', - titleIcon: '@', - tableKey: '@', - orderBy: '@', - reverseOrder: '<', - removeAction: '<', - retrievePage: '<', - }, -}); diff --git a/app/portainer/components/datatables/endpoints-datatable/endpointsDatatableController.js b/app/portainer/components/datatables/endpoints-datatable/endpointsDatatableController.js deleted file mode 100644 index ccdd3178d..000000000 --- a/app/portainer/components/datatables/endpoints-datatable/endpointsDatatableController.js +++ /dev/null @@ -1,108 +0,0 @@ -import _ from 'lodash-es'; -import { isBE } from '@/react/portainer/feature-flags/feature-flags.service'; - -angular.module('portainer.app').controller('EndpointsDatatableController', [ - '$scope', - '$controller', - 'DatatableService', - 'PaginationService', - function ($scope, $controller, DatatableService, PaginationService) { - angular.extend(this, $controller('GenericDatatableController', { $scope: $scope })); - - this.isBE = isBE; - - this.state = Object.assign(this.state, { - orderBy: this.orderBy, - loading: true, - filteredDataSet: [], - totalFilteredDataset: 0, - pageNumber: 1, - }); - - this.paginationChanged = async function () { - try { - this.state.loading = true; - this.state.filteredDataSet = []; - const start = (this.state.pageNumber - 1) * this.state.paginatedItemLimit + 1; - const { endpoints, totalCount } = await this.retrievePage(start, this.state.paginatedItemLimit, this.state.textFilter); - this.state.filteredDataSet = endpoints; - this.state.totalFilteredDataSet = totalCount; - this.refreshSelectedItems(); - } finally { - this.state.loading = false; - } - }; - - this.onPageChange = function (newPageNumber) { - this.state.pageNumber = newPageNumber; - this.paginationChanged(); - }; - - this.setReferrer = function () { - window.localStorage.setItem('wizardReferrer', 'environments'); - }; - - /** - * Overridden - */ - this.onTextFilterChange = function () { - var filterValue = this.state.textFilter; - DatatableService.setDataTableTextFilters(this.tableKey, filterValue); - this.resetSelectionState(); - this.paginationChanged(); - }; - - /** - * Overriden - */ - this.uniq = function () { - return _.uniqBy(_.concat(this.state.filteredDataSet, this.state.selectedItems), 'Id'); - }; - - /** - * Overridden - */ - this.changePaginationLimit = function () { - PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit); - this.paginationChanged(); - }; - - this.refreshSelectedItems = function () { - _.forEach(this.state.filteredDataSet, (item) => { - if (_.filter(this.state.selectedItems, (i) => i.Id == item.Id).length > 0) { - item.Checked = true; - } - }); - }; - - /** - * Overridden - */ - this.$onInit = function () { - this.setDefaults(); - this.prepareTableFromDataset(); - - var storedOrder = DatatableService.getDataTableOrder(this.tableKey); - if (storedOrder !== null) { - this.state.reverseOrder = storedOrder.reverse; - this.state.orderBy = storedOrder.orderBy; - } - - var textFilter = DatatableService.getDataTableTextFilters(this.tableKey); - if (textFilter !== null) { - this.state.textFilter = textFilter; - this.onTextFilterChange(); - } - - var storedFilters = DatatableService.getDataTableFilters(this.tableKey); - if (storedFilters !== null) { - this.filters = storedFilters; - } - if (this.filters && this.filters.state) { - this.filters.state.open = false; - } - - this.paginationChanged(); - }; - }, -]); diff --git a/app/portainer/react/views/index.ts b/app/portainer/react/views/index.ts index 6b80ace4a..056049eaa 100644 --- a/app/portainer/react/views/index.ts +++ b/app/portainer/react/views/index.ts @@ -9,6 +9,7 @@ import { CreateAccessToken } from '@/react/portainer/account/CreateAccessTokenVi import { EdgeComputeSettingsView } from '@/react/portainer/settings/EdgeComputeView/EdgeComputeSettingsView'; import { withI18nSuspense } from '@/react-tools/withI18nSuspense'; import { EdgeAutoCreateScriptView } from '@/react/portainer/environments/EdgeAutoCreateScriptView'; +import { ListView as EnvironmentsListView } from '@/react/portainer/environments/ListView'; import { wizardModule } from './wizard'; import { teamsModule } from './teams'; @@ -44,4 +45,8 @@ export const viewsModule = angular withUIRouter(withReactQuery(withCurrentUser(EdgeComputeSettingsView))), ['onSubmit', 'settings'] ) + ) + .component( + 'environmentsListView', + r2a(withUIRouter(withReactQuery(withCurrentUser(EnvironmentsListView))), []) ).name; diff --git a/app/portainer/views/endpoints/endpoints.html b/app/portainer/views/endpoints/endpoints.html deleted file mode 100644 index 72ac0b64f..000000000 --- a/app/portainer/views/endpoints/endpoints.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - -
-
- -
-
diff --git a/app/portainer/views/endpoints/endpoints.js b/app/portainer/views/endpoints/endpoints.js deleted file mode 100644 index 7b42d0936..000000000 --- a/app/portainer/views/endpoints/endpoints.js +++ /dev/null @@ -1,6 +0,0 @@ -import { EndpointsController } from './endpointsController'; - -angular.module('portainer.app').component('endpointsView', { - templateUrl: './endpoints.html', - controller: EndpointsController, -}); diff --git a/app/portainer/views/endpoints/endpointsController.js b/app/portainer/views/endpoints/endpointsController.js deleted file mode 100644 index acc2142ef..000000000 --- a/app/portainer/views/endpoints/endpointsController.js +++ /dev/null @@ -1,70 +0,0 @@ -import { map } from 'lodash'; -import EndpointHelper from '@/portainer/helpers/endpointHelper'; -import { getEnvironments } from '@/react/portainer/environments/environment.service'; -import { confirmDelete } from '@@/modals/confirm'; - -export class EndpointsController { - /* @ngInject */ - constructor($state, $async, EndpointService, GroupService, Notifications, EndpointProvider, StateManager) { - Object.assign(this, { - $state, - $async, - EndpointService, - GroupService, - Notifications, - EndpointProvider, - StateManager, - }); - - this.state = { - loadingMessage: '', - }; - - this.setLoadingMessage = this.setLoadingMessage.bind(this); - this.getPaginatedEndpoints = this.getPaginatedEndpoints.bind(this); - this.removeAction = this.removeAction.bind(this); - } - - setLoadingMessage(message) { - this.state.loadingMessage = message; - } - - removeAction(endpoints) { - confirmDelete('This action will remove all configurations associated to your environment(s). Continue?').then((confirmed) => { - if (!confirmed) { - return; - } - return this.$async(async () => { - try { - await Promise.all(endpoints.map(({ Id }) => this.EndpointService.deleteEndpoint(Id))); - this.Notifications.success('Environments successfully removed', map(endpoints, 'Name').join(', ')); - } catch (err) { - this.Notifications.error('Failure', err, 'Unable to remove environment'); - } - - const id = this.EndpointProvider.endpointID(); - // If the current endpoint was deleted, then clean endpoint store - if (endpoints.some((e) => e.Id === id)) { - this.StateManager.cleanEndpoint(); - } - - this.$state.reload(); - }); - }); - } - - getPaginatedEndpoints(start, limit, search) { - return this.$async(async () => { - try { - const [{ value: endpoints, totalCount }, groups] = await Promise.all([ - getEnvironments({ start, limit, query: { search, excludeSnapshots: true } }), - this.GroupService.groups(), - ]); - EndpointHelper.mapGroupNameToEndpoint(endpoints, groups); - return { endpoints, totalCount }; - } catch (err) { - this.Notifications.error('Failure', err, 'Unable to retrieve environment information'); - } - }); - } -} diff --git a/app/react-tools/test-mocks.ts b/app/react-tools/test-mocks.ts index 6efc2bb0b..0972f50cd 100644 --- a/app/react-tools/test-mocks.ts +++ b/app/react-tools/test-mocks.ts @@ -116,5 +116,9 @@ export function createMockEnvironment(): Environment { EndTime: '', StartTime: '', }, + StatusMessage: { + Detail: '', + Summary: '', + }, }; } diff --git a/app/react/portainer/HomeView/EnvironmentList/EnvironmentList.tsx b/app/react/portainer/HomeView/EnvironmentList/EnvironmentList.tsx index 770c4652e..f7b33e17a 100644 --- a/app/react/portainer/HomeView/EnvironmentList/EnvironmentList.tsx +++ b/app/react/portainer/HomeView/EnvironmentList/EnvironmentList.tsx @@ -126,7 +126,7 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) { pageLimit, ...queryWithSort, }, - refetchIfAnyOffline + { refetchInterval: refetchIfAnyOffline } ); useEffect(() => { diff --git a/app/react/portainer/environments/ListView/.keep b/app/react/portainer/environments/ListView/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/react/portainer/environments/ListView/EnvironmentsDatatable.tsx b/app/react/portainer/environments/ListView/EnvironmentsDatatable.tsx new file mode 100644 index 000000000..eb2b6f3a6 --- /dev/null +++ b/app/react/portainer/environments/ListView/EnvironmentsDatatable.tsx @@ -0,0 +1,104 @@ +import { HardDrive, Plus, Trash2 } from 'lucide-react'; +import { useState } from 'react'; + +import { useEnvironmentList } from '@/react/portainer/environments/queries'; +import { useGroups } from '@/react/portainer/environments/environment-groups/queries'; + +import { Datatable } from '@@/datatables'; +import { createPersistedStore } from '@@/datatables/types'; +import { Button } from '@@/buttons'; +import { Link } from '@@/Link'; +import { useTableState } from '@@/datatables/useTableState'; + +import { isBE } from '../../feature-flags/feature-flags.service'; +import { refetchIfAnyOffline } from '../queries/useEnvironmentList'; + +import { columns } from './columns'; +import { EnvironmentListItem } from './types'; +import { ImportFdoDeviceButton } from './ImportFdoDeviceButton'; + +const tableKey = 'environments'; +const settingsStore = createPersistedStore(tableKey, 'Name'); + +export function EnvironmentsDatatable({ + onRemove, +}: { + onRemove: (environments: Array) => void; +}) { + const tableState = useTableState(settingsStore, tableKey); + + const [page, setPage] = useState(0); + + const groupsQuery = useGroups(); + const { environments, isLoading, totalCount } = useEnvironmentList( + { + search: tableState.search, + excludeSnapshots: true, + page: page + 1, + pageLimit: tableState.pageSize, + sort: tableState.sortBy.id, + order: tableState.sortBy.desc ? 'desc' : 'asc', + }, + { enabled: groupsQuery.isSuccess, refetchInterval: refetchIfAnyOffline } + ); + + const environmentsWithGroups = environments.map( + (env) => { + const groupId = env.GroupId; + const group = groupsQuery.data?.find((g) => g.Id === groupId); + return { + ...env, + GroupName: group?.Name, + }; + } + ); + + return ( + ( +
+ + + + + {isBE && ( + + )} + + +
+ )} + /> + ); +} diff --git a/app/react/portainer/environments/ListView/ListView.tsx b/app/react/portainer/environments/ListView/ListView.tsx new file mode 100644 index 000000000..94f01b591 --- /dev/null +++ b/app/react/portainer/environments/ListView/ListView.tsx @@ -0,0 +1,58 @@ +import { useStore } from 'zustand'; +import _ from 'lodash'; + +import { environmentStore } from '@/react/hooks/current-environment-store'; +import { notifySuccess } from '@/portainer/services/notifications'; + +import { PageHeader } from '@@/PageHeader'; +import { confirmDelete } from '@@/modals/confirm'; + +import { Environment } from '../types'; + +import { EnvironmentsDatatable } from './EnvironmentsDatatable'; +import { useDeleteEnvironmentsMutation } from './useDeleteEnvironmentsMutation'; + +export function ListView() { + const constCurrentEnvironmentStore = useStore(environmentStore); + const deletionMutation = useDeleteEnvironmentsMutation(); + + return ( + <> + + + + + ); + + async function handleRemove(environments: Array) { + const confirmed = await confirmDelete( + 'This action will remove all configurations associated to your environment(s). Continue?' + ); + + if (!confirmed) { + return; + } + + const id = constCurrentEnvironmentStore.environmentId; + // If the current endpoint was deleted, then clean endpoint store + if (environments.some((e) => e.Id === id)) { + constCurrentEnvironmentStore.clear(); + } + + deletionMutation.mutate( + environments.map((e) => e.Id), + { + onSuccess() { + notifySuccess( + 'Environments successfully removed', + _.map(environments, 'Name').join(', ') + ); + }, + } + ); + } +} diff --git a/app/react/portainer/environments/ListView/columns/actions.tsx b/app/react/portainer/environments/ListView/columns/actions.tsx new file mode 100644 index 000000000..a52bad72e --- /dev/null +++ b/app/react/portainer/environments/ListView/columns/actions.tsx @@ -0,0 +1,41 @@ +import { CellContext } from '@tanstack/react-table'; +import { Users } from 'lucide-react'; + +import { EnvironmentStatus } from '@/react/portainer/environments/types'; + +import { Button } from '@@/buttons'; +import { Link } from '@@/Link'; + +import { EnvironmentListItem } from '../types'; + +import { columnHelper } from './helper'; + +export const actions = columnHelper.display({ + header: 'Actions', + cell: Cell, +}); + +function Cell({ + row: { original: environment }, +}: CellContext) { + if ( + environment.Status === EnvironmentStatus.Provisioning || + environment.Status === EnvironmentStatus.Error + ) { + return <>-; + } + + return ( + + ); +} diff --git a/app/react/portainer/environments/ListView/columns/helper.ts b/app/react/portainer/environments/ListView/columns/helper.ts new file mode 100644 index 000000000..3d35eef7f --- /dev/null +++ b/app/react/portainer/environments/ListView/columns/helper.ts @@ -0,0 +1,5 @@ +import { createColumnHelper } from '@tanstack/react-table'; + +import { EnvironmentListItem } from '../types'; + +export const columnHelper = createColumnHelper(); diff --git a/app/react/portainer/environments/ListView/columns/index.ts b/app/react/portainer/environments/ListView/columns/index.ts new file mode 100644 index 000000000..7a68d2272 --- /dev/null +++ b/app/react/portainer/environments/ListView/columns/index.ts @@ -0,0 +1,15 @@ +import { actions } from './actions'; +import { columnHelper } from './helper'; +import { name } from './name'; +import { type } from './type'; +import { url } from './url'; + +export const columns = [ + name, + type, + url, + columnHelper.accessor('GroupName', { + header: 'Group Name', + }), + actions, +]; diff --git a/app/react/portainer/environments/ListView/columns/name.tsx b/app/react/portainer/environments/ListView/columns/name.tsx new file mode 100644 index 000000000..92821e226 --- /dev/null +++ b/app/react/portainer/environments/ListView/columns/name.tsx @@ -0,0 +1,21 @@ +import { EnvironmentStatus } from '@/react/portainer/environments/types'; + +import { Link } from '@@/Link'; + +import { columnHelper } from './helper'; + +export const name = columnHelper.accessor('Name', { + header: 'Name', + cell: ({ getValue, row: { original: environment } }) => { + const name = getValue(); + if (environment.Status === EnvironmentStatus.Provisioning) { + return name; + } + + return ( + + {name} + + ); + }, +}); diff --git a/app/react/portainer/environments/ListView/columns/type.tsx b/app/react/portainer/environments/ListView/columns/type.tsx new file mode 100644 index 000000000..5f82deace --- /dev/null +++ b/app/react/portainer/environments/ListView/columns/type.tsx @@ -0,0 +1,28 @@ +import { CellContext } from '@tanstack/react-table'; + +import { environmentTypeIcon } from '@/portainer/filters/filters'; +import { + Environment, + EnvironmentType, +} from '@/react/portainer/environments/types'; +import { getPlatformTypeName } from '@/react/portainer/environments/utils'; + +import { Icon } from '@@/Icon'; + +import { columnHelper } from './helper'; + +export const type = columnHelper.accessor('Type', { + header: 'Type', + cell: Cell, +}); + +function Cell({ getValue }: CellContext) { + const type = getValue(); + + return ( + + + {getPlatformTypeName(type)} + + ); +} diff --git a/app/react/portainer/environments/ListView/columns/url.tsx b/app/react/portainer/environments/ListView/columns/url.tsx new file mode 100644 index 000000000..a7c7635d7 --- /dev/null +++ b/app/react/portainer/environments/ListView/columns/url.tsx @@ -0,0 +1,116 @@ +import { CellContext } from '@tanstack/react-table'; +import { AlertCircle, HelpCircle, Settings } from 'lucide-react'; + +import { + EnvironmentStatus, + EnvironmentStatusMessage, + EnvironmentType, +} from '@/react/portainer/environments/types'; +import { notifySuccess } from '@/portainer/services/notifications'; + +import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren'; +import { Button } from '@@/buttons'; + +import { EnvironmentListItem } from '../types'; +import { useUpdateEnvironmentMutation } from '../../queries/useUpdateEnvironmentMutation'; + +import { columnHelper } from './helper'; + +export const url = columnHelper.accessor('URL', { + header: 'URL', + cell: Cell, +}); + +function Cell({ + row: { original: environment }, +}: CellContext) { + const mutation = useUpdateEnvironmentMutation(); + + if ( + environment.Type !== EnvironmentType.EdgeAgentOnDocker && + environment.Status !== EnvironmentStatus.Provisioning + ) { + return ( + <> + {environment.URL} + {environment.StatusMessage.Summary && + environment.StatusMessage.Detail && ( +
+ + + + {environment.StatusMessage.Detail} + {environment.URL && ( +
+ +
+ )} +
+ } + position="bottom" + > + + + + + )} + + ); + } + + if (environment.Type === 4) { + return <>-; + } + + if (environment.Status === 3) { + const status = ( + + + {environment.StatusMessage.Summary} + + ); + if (!environment.StatusMessage.Detail) { + return status; + } + return ( + + {status} + + ); + } + + return <>-; + + function handleDismissButton() { + mutation.mutate( + { + id: environment.Id, + payload: { + IsSetStatusMessage: true, + StatusMessage: {} as EnvironmentStatusMessage, + }, + }, + { + onSuccess: () => { + notifySuccess('Success', 'Error dismissed successfully'); + }, + } + ); + } +} diff --git a/app/react/portainer/environments/ListView/index.ts b/app/react/portainer/environments/ListView/index.ts new file mode 100644 index 000000000..dd06dfd19 --- /dev/null +++ b/app/react/portainer/environments/ListView/index.ts @@ -0,0 +1 @@ +export { ListView } from './ListView'; diff --git a/app/react/portainer/environments/ListView/types.ts b/app/react/portainer/environments/ListView/types.ts new file mode 100644 index 000000000..e0d7dfa48 --- /dev/null +++ b/app/react/portainer/environments/ListView/types.ts @@ -0,0 +1,5 @@ +import { Environment } from '@/react/portainer/environments/types'; + +export type EnvironmentListItem = { + GroupName?: string; +} & Environment; diff --git a/app/react/portainer/environments/ListView/useDeleteEnvironmentsMutation.ts b/app/react/portainer/environments/ListView/useDeleteEnvironmentsMutation.ts new file mode 100644 index 000000000..8c6df6c99 --- /dev/null +++ b/app/react/portainer/environments/ListView/useDeleteEnvironmentsMutation.ts @@ -0,0 +1,36 @@ +import { useMutation, useQueryClient } from 'react-query'; + +import { promiseSequence } from '@/portainer/helpers/promise-utils'; +import axios, { parseAxiosError } from '@/portainer/services/axios'; +import { + mutationOptions, + withError, + withInvalidate, +} from '@/react-tools/react-query'; + +import { buildUrl } from '../environment.service/utils'; +import { EnvironmentId } from '../types'; + +export function useDeleteEnvironmentsMutation() { + const queryClient = useQueryClient(); + return useMutation( + (environments: EnvironmentId[]) => + promiseSequence( + environments.map( + (environmentId) => () => deleteEnvironment(environmentId) + ) + ), + mutationOptions( + withError('Unable to delete environment(s)'), + withInvalidate(queryClient, [['environments']]) + ) + ); +} + +async function deleteEnvironment(id: EnvironmentId) { + try { + await axios.delete(buildUrl(id)); + } catch (e) { + throw parseAxiosError(e as Error, 'Unable to delete environment'); + } +} diff --git a/app/react/portainer/environments/queries/useEnvironmentList.ts b/app/react/portainer/environments/queries/useEnvironmentList.ts index 6fbd9cb2b..00675b8e4 100644 --- a/app/react/portainer/environments/queries/useEnvironmentList.ts +++ b/app/react/portainer/environments/queries/useEnvironmentList.ts @@ -39,12 +39,18 @@ export function refetchIfAnyOffline(data?: GetEndpointsResponse) { export function useEnvironmentList( { page = 1, pageLimit = 100, sort, order, ...query }: Query = {}, - refetchInterval?: - | number - | false - | ((data?: GetEndpointsResponse) => false | number), - staleTime = 0, - enabled = true + { + enabled, + refetchInterval, + staleTime, + }: { + refetchInterval?: + | number + | false + | ((data?: GetEndpointsResponse) => false | number); + staleTime?: number; + enabled?: boolean; + } = {} ) { const { isLoading, data } = useQuery( [ diff --git a/app/react/portainer/environments/queries/useUpdateEnvironmentMutation.ts b/app/react/portainer/environments/queries/useUpdateEnvironmentMutation.ts index d9330c14c..ecb474124 100644 --- a/app/react/portainer/environments/queries/useUpdateEnvironmentMutation.ts +++ b/app/react/portainer/environments/queries/useUpdateEnvironmentMutation.ts @@ -1,21 +1,23 @@ -import { useMutation, useQueryClient } from 'react-query'; +import { useQueryClient, useMutation } from 'react-query'; +import { withError, withInvalidate } from '@/react-tools/react-query'; +import { + EnvironmentId, + EnvironmentStatusMessage, + Environment, +} from '@/react/portainer/environments/types'; import axios, { parseAxiosError } from '@/portainer/services/axios'; import { TagId } from '@/portainer/tags/types'; -import { withError } from '@/react-tools/react-query'; import { EnvironmentGroupId } from '../environment-groups/types'; import { buildUrl } from '../environment.service/utils'; -import { EnvironmentId, Environment } from '../types'; import { queryKeys } from './query-keys'; export function useUpdateEnvironmentMutation() { const queryClient = useQueryClient(); return useMutation(updateEnvironment, { - onSuccess(data, { id }) { - queryClient.invalidateQueries(queryKeys.item(id)); - }, + ...withInvalidate(queryClient, [queryKeys.base()]), ...withError('Unable to update environment'), }); } @@ -38,6 +40,9 @@ export interface UpdatePayload { AzureApplicationID: string; AzureTenantID: string; AzureAuthenticationKey: string; + + IsSetStatusMessage: boolean; + StatusMessage: Partial; } async function updateEnvironment({ diff --git a/app/react/portainer/environments/types.ts b/app/react/portainer/environments/types.ts index 3536bd2d6..f93ce9338 100644 --- a/app/react/portainer/environments/types.ts +++ b/app/react/portainer/environments/types.ts @@ -131,6 +131,10 @@ interface EndpointChangeWindow { StartTime: string; EndTime: string; } +export interface EnvironmentStatusMessage { + Summary: string; + Detail: string; +} export type Environment = { Agent: { Version: string }; @@ -163,6 +167,10 @@ export type Environment = { /** GitOps update change window restriction for stacks and apps */ ChangeWindow: EndpointChangeWindow; + /** + * A message that describes the status. Should be included for Status Provisioning or Error. + */ + StatusMessage: EnvironmentStatusMessage; }; /** diff --git a/app/react/portainer/environments/update-schedules/common/useEnvironments.ts b/app/react/portainer/environments/update-schedules/common/useEnvironments.ts index ee1f91d7b..e5f776765 100644 --- a/app/react/portainer/environments/update-schedules/common/useEnvironments.ts +++ b/app/react/portainer/environments/update-schedules/common/useEnvironments.ts @@ -4,9 +4,9 @@ import { EdgeTypes, EnvironmentId } from '@/react/portainer/environments/types'; export function useEnvironments(environmentsIds: Array) { const environmentsQuery = useEnvironmentList( { endpointIds: environmentsIds, types: EdgeTypes }, - undefined, - undefined, - environmentsIds.length > 0 + { + enabled: environmentsIds.length > 0, + } ); return environmentsQuery.environments; diff --git a/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardEndpointsList/WizardEndpointsList.tsx b/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardEndpointsList/WizardEndpointsList.tsx index a4ea97eb8..50a884f28 100644 --- a/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardEndpointsList/WizardEndpointsList.tsx +++ b/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardEndpointsList/WizardEndpointsList.tsx @@ -28,19 +28,21 @@ interface Props { export function WizardEndpointsList({ environmentIds }: Props) { const { environments } = useEnvironmentList( { endpointIds: environmentIds }, - (environments) => { - if (!environments) { - return false; - } + { + refetchInterval: (environments) => { + if (!environments) { + return false; + } - if (!environments.value.some(isUnassociatedEdgeEnvironment)) { - return false; - } + if (!environments.value.some(isUnassociatedEdgeEnvironment)) { + return false; + } - return ENVIRONMENTS_POLLING_INTERVAL; - }, - 0, - environmentIds.length > 0 + return ENVIRONMENTS_POLLING_INTERVAL; + }, + + enabled: environmentIds.length > 0, + } ); return ( diff --git a/app/react/portainer/environments/wizard/HomeView/useFetchOrCreateLocalEnvironment.ts b/app/react/portainer/environments/wizard/HomeView/useFetchOrCreateLocalEnvironment.ts index 72faf893d..6437472a8 100644 --- a/app/react/portainer/environments/wizard/HomeView/useFetchOrCreateLocalEnvironment.ts +++ b/app/react/portainer/environments/wizard/HomeView/useFetchOrCreateLocalEnvironment.ts @@ -76,8 +76,10 @@ function useFetchLocalEnvironment() { pageLimit: 1, types: [EnvironmentType.Docker, EnvironmentType.KubernetesLocal], }, - false, - Infinity + { + refetchInterval: false, + staleTime: Infinity, + } ); return {