From 5c37ed328fd07c1d998e06374a483fb87551e891 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 11 Oct 2023 10:27:42 +0300 Subject: [PATCH] refactor(docker/volumes): migrate table to react [EE-4677] (#10312) --- .../volumes-datatable/volumesDatatable.html | 242 ------------------ .../volumes-datatable/volumesDatatable.js | 16 -- .../volumesDatatableController.js | 71 ----- app/docker/models/volume.js | 26 -- app/docker/models/volume.ts | 56 ++++ app/docker/react/components/index.ts | 2 + app/docker/react/components/volumes.ts | 18 ++ app/docker/views/volumes/volumes.html | 16 +- app/portainer/filters/filters.js | 16 +- app/portainer/filters/index.js | 2 +- .../ListView/columns/ownership.tsx | 2 +- app/react/docker/.keep | 0 .../datatable/createOwnershipColumn.tsx | 15 +- app/react/docker/volumes/.keep | 0 app/react/docker/volumes/ListView/.keep | 0 .../VolumesDatatable/TableActions.tsx | 44 ++++ .../VolumesDatatable/VolumesDatatable.tsx | 72 ++++++ .../VolumesDatatable/columns/helper.ts | 5 + .../VolumesDatatable/columns/index.ts | 48 ++++ .../VolumesDatatable/columns/name.tsx | 102 ++++++++ .../ListView/VolumesDatatable/index.ts | 1 + .../ListView/VolumesDatatable/tableMeta.ts | 23 ++ app/react/docker/volumes/ListView/types.ts | 3 + .../AccessControlPanelDetails.tsx | 3 +- .../access-control/EditDetails/useOptions.tsx | 14 +- 25 files changed, 402 insertions(+), 395 deletions(-) delete mode 100644 app/docker/components/datatables/volumes-datatable/volumesDatatable.html delete mode 100644 app/docker/components/datatables/volumes-datatable/volumesDatatable.js delete mode 100644 app/docker/components/datatables/volumes-datatable/volumesDatatableController.js delete mode 100644 app/docker/models/volume.js create mode 100644 app/docker/models/volume.ts create mode 100644 app/docker/react/components/volumes.ts delete mode 100644 app/react/docker/.keep delete mode 100644 app/react/docker/volumes/.keep delete mode 100644 app/react/docker/volumes/ListView/.keep create mode 100644 app/react/docker/volumes/ListView/VolumesDatatable/TableActions.tsx create mode 100644 app/react/docker/volumes/ListView/VolumesDatatable/VolumesDatatable.tsx create mode 100644 app/react/docker/volumes/ListView/VolumesDatatable/columns/helper.ts create mode 100644 app/react/docker/volumes/ListView/VolumesDatatable/columns/index.ts create mode 100644 app/react/docker/volumes/ListView/VolumesDatatable/columns/name.tsx create mode 100644 app/react/docker/volumes/ListView/VolumesDatatable/index.ts create mode 100644 app/react/docker/volumes/ListView/VolumesDatatable/tableMeta.ts create mode 100644 app/react/docker/volumes/ListView/types.ts diff --git a/app/docker/components/datatables/volumes-datatable/volumesDatatable.html b/app/docker/components/datatables/volumes-datatable/volumesDatatable.html deleted file mode 100644 index 80a8918bc..000000000 --- a/app/docker/components/datatables/volumes-datatable/volumesDatatable.html +++ /dev/null @@ -1,242 +0,0 @@ -
- - -
-
-
- -
- {{ $ctrl.titleText }} -
- -
- - -
-
- - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - Filter - Filter - - - - - - - - - - - - - -
- - - - - {{ item.Id | truncate: 40 }} - - - - Unused - {{ item.StackName ? item.StackName : '-' }}{{ item.Driver }}{{ item.Mountpoint | truncatelr }}{{ item.CreatedAt | getisodate }}{{ item.NodeName ? item.NodeName : '-' }} - - - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }} - -
Loading...
No volume available.
-
- -
-
-
diff --git a/app/docker/components/datatables/volumes-datatable/volumesDatatable.js b/app/docker/components/datatables/volumes-datatable/volumesDatatable.js deleted file mode 100644 index cabc07898..000000000 --- a/app/docker/components/datatables/volumes-datatable/volumesDatatable.js +++ /dev/null @@ -1,16 +0,0 @@ -angular.module('portainer.docker').component('volumesDatatable', { - templateUrl: './volumesDatatable.html', - controller: 'VolumesDatatableController', - bindings: { - titleText: '@', - titleIcon: '@', - dataset: '<', - tableKey: '@', - orderBy: '@', - reverseOrder: '<', - showHostColumn: '<', - removeAction: '<', - showBrowseAction: '<', - refreshCallback: '<', - }, -}); diff --git a/app/docker/components/datatables/volumes-datatable/volumesDatatableController.js b/app/docker/components/datatables/volumes-datatable/volumesDatatableController.js deleted file mode 100644 index 23efd28ee..000000000 --- a/app/docker/components/datatables/volumes-datatable/volumesDatatableController.js +++ /dev/null @@ -1,71 +0,0 @@ -angular.module('portainer.docker').controller('VolumesDatatableController', [ - '$scope', - '$controller', - 'DatatableService', - function ($scope, $controller, DatatableService) { - angular.extend(this, $controller('GenericDatatableController', { $scope: $scope })); - - var ctrl = this; - - this.filters = { - state: { - open: false, - enabled: false, - showUsedVolumes: true, - showUnusedVolumes: true, - }, - }; - - this.applyFilters = function (value) { - var volume = value; - var filters = ctrl.filters; - if ((volume.dangling && filters.state.showUnusedVolumes) || (!volume.dangling && filters.state.showUsedVolumes)) { - return true; - } - return false; - }; - - this.onstateFilterChange = function () { - var filters = this.filters.state; - var filtered = false; - if (!filters.showUsedVolumes || !filters.showUnusedVolumes) { - filtered = true; - } - this.filters.state.enabled = filtered; - DatatableService.setDataTableFilters(this.tableKey, this.filters); - }; - - this.$onInit = function () { - this.setDefaults(); - this.prepareTableFromDataset(); - - this.state.orderBy = this.orderBy; - 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; - } - - var storedSettings = DatatableService.getDataTableSettings(this.tableKey); - if (storedSettings !== null) { - this.settings = storedSettings; - this.settings.open = false; - } - this.onSettingsRepeaterChange(); - }; - }, -]); diff --git a/app/docker/models/volume.js b/app/docker/models/volume.js deleted file mode 100644 index 3910f0c8f..000000000 --- a/app/docker/models/volume.js +++ /dev/null @@ -1,26 +0,0 @@ -import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel'; - -export function VolumeViewModel(data) { - this.Id = data.Name; - this.CreatedAt = data.CreatedAt; - this.Driver = data.Driver; - this.Options = data.Options; - this.Labels = data.Labels; - if (this.Labels && this.Labels['com.docker.compose.project']) { - this.StackName = this.Labels['com.docker.compose.project']; - } else if (this.Labels && this.Labels['com.docker.stack.namespace']) { - this.StackName = this.Labels['com.docker.stack.namespace']; - } - this.Mountpoint = data.Mountpoint; - - this.ResourceId = data.ResourceID; - - if (data.Portainer) { - if (data.Portainer.ResourceControl) { - this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl); - } - if (data.Portainer.Agent && data.Portainer.Agent.NodeName) { - this.NodeName = data.Portainer.Agent.NodeName; - } - } -} diff --git a/app/docker/models/volume.ts b/app/docker/models/volume.ts new file mode 100644 index 000000000..260011504 --- /dev/null +++ b/app/docker/models/volume.ts @@ -0,0 +1,56 @@ +import { Volume } from 'docker-types/generated/1.41'; + +import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel'; +import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn'; +import { PortainerMetadata } from '@/react/docker/types'; + +export class VolumeViewModel implements IResource { + Id: string; + + CreatedAt: string | undefined; + + Driver: string; + + Options: Record; + + Labels: Record; + + StackName?: string; + + Mountpoint: string; + + ResourceId?: string; + + NodeName?: string; + + ResourceControl?: ResourceControlViewModel; + + constructor( + data: Volume & { Portainer?: PortainerMetadata; ResourceID?: string } + ) { + this.Id = data.Name; + this.CreatedAt = data.CreatedAt; + this.Driver = data.Driver; + this.Options = data.Options; + this.Labels = data.Labels; + if (this.Labels && this.Labels['com.docker.compose.project']) { + this.StackName = this.Labels['com.docker.compose.project']; + } else if (this.Labels && this.Labels['com.docker.stack.namespace']) { + this.StackName = this.Labels['com.docker.stack.namespace']; + } + this.Mountpoint = data.Mountpoint; + + this.ResourceId = data.ResourceID; + + if (data.Portainer) { + if (data.Portainer.ResourceControl) { + this.ResourceControl = new ResourceControlViewModel( + data.Portainer.ResourceControl + ); + } + if (data.Portainer.Agent && data.Portainer.Agent.NodeName) { + this.NodeName = data.Portainer.Agent.NodeName; + } + } + } +} diff --git a/app/docker/react/components/index.ts b/app/docker/react/components/index.ts index c4163f15e..759b4e635 100644 --- a/app/docker/react/components/index.ts +++ b/app/docker/react/components/index.ts @@ -28,6 +28,7 @@ import { containersModule } from './containers'; import { servicesModule } from './services'; import { networksModule } from './networks'; import { swarmModule } from './swarm'; +import { volumesModule } from './volumes'; const ngModule = angular .module('portainer.docker.react.components', [ @@ -35,6 +36,7 @@ const ngModule = angular servicesModule, networksModule, swarmModule, + volumesModule, ]) .component('dockerfileDetails', r2a(DockerfileDetails, ['image'])) .component('dockerHealthStatus', r2a(HealthStatus, ['health'])) diff --git a/app/docker/react/components/volumes.ts b/app/docker/react/components/volumes.ts new file mode 100644 index 000000000..994e9ec7b --- /dev/null +++ b/app/docker/react/components/volumes.ts @@ -0,0 +1,18 @@ +import angular from 'angular'; + +import { r2a } from '@/react-tools/react2angular'; +import { withUIRouter } from '@/react-tools/withUIRouter'; +import { VolumesDatatable } from '@/react/docker/volumes/ListView/VolumesDatatable'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; + +export const volumesModule = angular + .module('portainer.docker.react.components.volumes', []) + .component( + 'volumesDatatable', + r2a(withUIRouter(withCurrentUser(VolumesDatatable)), [ + 'dataset', + 'onRemove', + 'onRefresh', + 'isBrowseVisible', + ]) + ).name; diff --git a/app/docker/views/volumes/volumes.html b/app/docker/views/volumes/volumes.html index 93c4be0ff..ffa186a68 100644 --- a/app/docker/views/volumes/volumes.html +++ b/app/docker/views/volumes/volumes.html @@ -1,17 +1,3 @@ -
-
- -
-
+ diff --git a/app/portainer/filters/filters.js b/app/portainer/filters/filters.js index f4f875d32..1ea28326c 100644 --- a/app/portainer/filters/filters.js +++ b/app/portainer/filters/filters.js @@ -1,13 +1,12 @@ import moment from 'moment'; import _ from 'lodash-es'; import filesize from 'filesize'; -import { Eye, EyeOff, Users, Cloud } from 'lucide-react'; +import { Cloud } from 'lucide-react'; import Kube from '@/assets/ico/kube.svg?c'; import DockerIcon from '@/assets/ico/vendor/docker-icon.svg?c'; import MicrosoftIcon from '@/assets/ico/vendor/microsoft-icon.svg?c'; import NomadIcon from '@/assets/ico/vendor/nomad-icon.svg?c'; -import { ResourceControlOwnership as RCO } from '@/react/portainer/access-control/types'; import { EnvironmentType } from '@/react/portainer/environments/types'; export function truncateLeftRight(text, max, left, right) { @@ -127,19 +126,6 @@ export function environmentTypeIcon(type) { } } -export function ownershipIcon(ownership) { - switch (ownership) { - case RCO.PRIVATE: - return EyeOff; - case RCO.ADMINISTRATORS: - return EyeOff; - case RCO.RESTRICTED: - return Users; - default: - return Eye; - } -} - export function truncate(text, length, end) { if (isNaN(length)) { length = 10; diff --git a/app/portainer/filters/index.js b/app/portainer/filters/index.js index 28a9e1742..0e817d29f 100644 --- a/app/portainer/filters/index.js +++ b/app/portainer/filters/index.js @@ -1,6 +1,7 @@ import angular from 'angular'; import _ from 'lodash-es'; +import { ownershipIcon } from '@/react/docker/components/datatable/createOwnershipColumn'; import { arrayToStr, environmentTypeIcon, @@ -12,7 +13,6 @@ import { isoDate, isoDateFromTimestamp, labelsToStr, - ownershipIcon, stripProtocol, truncate, truncateLeftRight, diff --git a/app/react/azure/container-instances/ListView/columns/ownership.tsx b/app/react/azure/container-instances/ListView/columns/ownership.tsx index 0f0d88101..29843b32c 100644 --- a/app/react/azure/container-instances/ListView/columns/ownership.tsx +++ b/app/react/azure/container-instances/ListView/columns/ownership.tsx @@ -1,10 +1,10 @@ import clsx from 'clsx'; import { CellContext } from '@tanstack/react-table'; -import { ownershipIcon } from '@/portainer/filters/filters'; import { ResourceControlOwnership } from '@/react/portainer/access-control/types'; import { ContainerGroup } from '@/react/azure/types'; import { determineOwnership } from '@/react/portainer/access-control/models/ResourceControlViewModel'; +import { ownershipIcon } from '@/react/docker/components/datatable/createOwnershipColumn'; import { columnHelper } from './helper'; diff --git a/app/react/docker/.keep b/app/react/docker/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/react/docker/components/datatable/createOwnershipColumn.tsx b/app/react/docker/components/datatable/createOwnershipColumn.tsx index 3a4b70d6e..feba754b9 100644 --- a/app/react/docker/components/datatable/createOwnershipColumn.tsx +++ b/app/react/docker/components/datatable/createOwnershipColumn.tsx @@ -1,6 +1,6 @@ import { CellContext, ColumnDef } from '@tanstack/react-table'; +import { Eye, EyeOff, Users } from 'lucide-react'; -import { ownershipIcon } from '@/portainer/filters/filters'; import { ResourceControlOwnership } from '@/react/portainer/access-control/types'; import { Icon } from '@@/Icon'; @@ -36,3 +36,16 @@ export function createOwnershipColumn( ); } } + +export function ownershipIcon(ownership: ResourceControlOwnership) { + switch (ownership) { + case ResourceControlOwnership.PRIVATE: + return EyeOff; + case ResourceControlOwnership.ADMINISTRATORS: + return EyeOff; + case ResourceControlOwnership.RESTRICTED: + return Users; + default: + return Eye; + } +} diff --git a/app/react/docker/volumes/.keep b/app/react/docker/volumes/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/react/docker/volumes/ListView/.keep b/app/react/docker/volumes/ListView/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/react/docker/volumes/ListView/VolumesDatatable/TableActions.tsx b/app/react/docker/volumes/ListView/VolumesDatatable/TableActions.tsx new file mode 100644 index 000000000..fb30652fd --- /dev/null +++ b/app/react/docker/volumes/ListView/VolumesDatatable/TableActions.tsx @@ -0,0 +1,44 @@ +import { Plus, Trash2 } from 'lucide-react'; + +import { Authorized } from '@/react/hooks/useUser'; + +import { Link } from '@@/Link'; +import { Button } from '@@/buttons'; + +import { DecoratedVolume } from '../types'; + +export function TableActions({ + selectedItems, + onRemove, +}: { + selectedItems: Array; + onRemove(items: Array): void; +}) { + return ( +
+ + + + + + +
+ ); +} diff --git a/app/react/docker/volumes/ListView/VolumesDatatable/VolumesDatatable.tsx b/app/react/docker/volumes/ListView/VolumesDatatable/VolumesDatatable.tsx new file mode 100644 index 000000000..9c75f743c --- /dev/null +++ b/app/react/docker/volumes/ListView/VolumesDatatable/VolumesDatatable.tsx @@ -0,0 +1,72 @@ +import { Database } from 'lucide-react'; + +import { Datatable, TableSettingsMenu } from '@@/datatables'; +import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh'; +import { + BasicTableSettings, + RefreshableTableSettings, + createPersistedStore, + refreshableSettings, +} from '@@/datatables/types'; +import { useRepeater } from '@@/datatables/useRepeater'; +import { useTableState } from '@@/datatables/useTableState'; +import { withMeta } from '@@/datatables/extend-options/withMeta'; + +import { DecoratedVolume } from '../types'; + +import { TableActions } from './TableActions'; +import { useColumns } from './columns'; + +interface TableSettings extends BasicTableSettings, RefreshableTableSettings {} + +const storageKey = 'docker-volumes'; +const store = createPersistedStore( + storageKey, + undefined, + (set) => ({ + ...refreshableSettings(set), + }) +); + +export function VolumesDatatable({ + dataset, + onRemove, + onRefresh, + isBrowseVisible, +}: { + dataset?: Array; + onRemove(items: Array): void; + onRefresh(): Promise; + isBrowseVisible: boolean; +}) { + const tableState = useTableState(store, storageKey); + useRepeater(tableState.autoRefreshRate, onRefresh); + const columns = useColumns(); + + return ( + ( + + )} + renderTableSettings={() => ( + + tableState.setAutoRefreshRate(value)} + /> + + )} + extendTableOptions={withMeta({ + table: 'volumes', + isBrowseVisible, + })} + /> + ); +} diff --git a/app/react/docker/volumes/ListView/VolumesDatatable/columns/helper.ts b/app/react/docker/volumes/ListView/VolumesDatatable/columns/helper.ts new file mode 100644 index 000000000..870bfb2f0 --- /dev/null +++ b/app/react/docker/volumes/ListView/VolumesDatatable/columns/helper.ts @@ -0,0 +1,5 @@ +import { createColumnHelper } from '@tanstack/react-table'; + +import { DecoratedVolume } from '../../types'; + +export const columnHelper = createColumnHelper(); diff --git a/app/react/docker/volumes/ListView/VolumesDatatable/columns/index.ts b/app/react/docker/volumes/ListView/VolumesDatatable/columns/index.ts new file mode 100644 index 000000000..18a44d511 --- /dev/null +++ b/app/react/docker/volumes/ListView/VolumesDatatable/columns/index.ts @@ -0,0 +1,48 @@ +import _ from 'lodash'; +import { useMemo } from 'react'; + +import { useIsSwarm } from '@/react/docker/proxy/queries/useInfo'; +import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; +import { createOwnershipColumn } from '@/react/docker/components/datatable/createOwnershipColumn'; +import { isoDate, truncateLeftRight } from '@/portainer/filters/filters'; + +import { DecoratedVolume } from '../../types'; + +import { columnHelper } from './helper'; +import { name } from './name'; + +export function useColumns() { + const environmentId = useEnvironmentId(); + const isSwarm = useIsSwarm(environmentId); + + return useMemo( + () => + _.compact([ + name, + columnHelper.accessor((item) => item.StackName || '-', { + header: 'Stack', + }), + columnHelper.accessor((item) => item.Driver, { + header: 'Driver', + }), + columnHelper.accessor((item) => item.Mountpoint, { + header: 'Mount point', + cell({ getValue }) { + return truncateLeftRight(getValue()); + }, + }), + columnHelper.accessor((item) => item.CreatedAt, { + header: 'Created', + cell({ getValue }) { + return isoDate(getValue()); + }, + }), + isSwarm && + columnHelper.accessor((item) => item.NodeName || '-', { + header: 'Host', + }), + createOwnershipColumn(), + ]), + [isSwarm] + ); +} diff --git a/app/react/docker/volumes/ListView/VolumesDatatable/columns/name.tsx b/app/react/docker/volumes/ListView/VolumesDatatable/columns/name.tsx new file mode 100644 index 000000000..a0df91d5c --- /dev/null +++ b/app/react/docker/volumes/ListView/VolumesDatatable/columns/name.tsx @@ -0,0 +1,102 @@ +import { CellContext, Column } from '@tanstack/react-table'; +import { Search } from 'lucide-react'; + +import { truncate } from '@/portainer/filters/filters'; +import { getValueAsArrayOfStrings } from '@/portainer/helpers/array'; +import { Authorized } from '@/react/hooks/useUser'; + +import { Button } from '@@/buttons'; +import { Link } from '@@/Link'; +import { MultipleSelectionFilter } from '@@/datatables/Filter'; + +import { DecoratedVolume } from '../../types'; +import { getTableMeta } from '../tableMeta'; + +import { columnHelper } from './helper'; + +export const name = columnHelper.accessor('Id', { + id: 'name', + header: 'Name', + cell: Cell, + enableColumnFilter: true, + filterFn: ( + { original: { dangling } }, + columnId, + filterValue: Array<'Used' | 'Unused'> + ) => { + if (filterValue.length === 0) { + return true; + } + + if (filterValue.includes('Used') && !dangling) { + return true; + } + + if (filterValue.includes('Unused') && dangling) { + return true; + } + + return false; + }, + meta: { + filter: FilterByUsage, + }, +}); + +function FilterByUsage({ + column: { getFilterValue, setFilterValue, id }, +}: { + column: Column; +}) { + const options = ['Used', 'Unused']; + + const value = getFilterValue(); + + const valueAsArray = getValueAsArrayOfStrings(value); + + return ( + + ); +} + +function Cell({ + getValue, + row: { original: item }, + table: { + options: { meta }, + }, +}: CellContext) { + const { isBrowseVisible } = getTableMeta(meta); + const name = getValue(); + + return ( + <> + + {truncate(name, 40)} + + {isBrowseVisible && ( + + + + )} + {item.dangling && ( + Unused + )} + + ); +} diff --git a/app/react/docker/volumes/ListView/VolumesDatatable/index.ts b/app/react/docker/volumes/ListView/VolumesDatatable/index.ts new file mode 100644 index 000000000..255fefdfb --- /dev/null +++ b/app/react/docker/volumes/ListView/VolumesDatatable/index.ts @@ -0,0 +1 @@ +export { VolumesDatatable } from './VolumesDatatable'; diff --git a/app/react/docker/volumes/ListView/VolumesDatatable/tableMeta.ts b/app/react/docker/volumes/ListView/VolumesDatatable/tableMeta.ts new file mode 100644 index 000000000..7b70057d0 --- /dev/null +++ b/app/react/docker/volumes/ListView/VolumesDatatable/tableMeta.ts @@ -0,0 +1,23 @@ +import { TableMeta as BaseTableMeta } from '@tanstack/react-table'; + +import { VolumeViewModel } from '@/docker/models/volume'; + +interface TableMeta { + isBrowseVisible: boolean; + table: 'volumes'; +} + +function isTableMeta(meta: BaseTableMeta): meta is TableMeta { + return meta.table === 'volumes'; +} + +export function getTableMeta(meta?: BaseTableMeta): TableMeta { + if (!meta || !isTableMeta(meta)) { + return { + isBrowseVisible: false, + table: 'volumes', + }; + } + + return meta; +} diff --git a/app/react/docker/volumes/ListView/types.ts b/app/react/docker/volumes/ListView/types.ts new file mode 100644 index 000000000..e6b7cd6d1 --- /dev/null +++ b/app/react/docker/volumes/ListView/types.ts @@ -0,0 +1,3 @@ +import { VolumeViewModel } from '@/docker/models/volume'; + +export type DecoratedVolume = VolumeViewModel & { dangling: boolean }; diff --git a/app/react/portainer/access-control/AccessControlPanel/AccessControlPanelDetails.tsx b/app/react/portainer/access-control/AccessControlPanel/AccessControlPanelDetails.tsx index 503493356..5a03b6c5b 100644 --- a/app/react/portainer/access-control/AccessControlPanel/AccessControlPanelDetails.tsx +++ b/app/react/portainer/access-control/AccessControlPanel/AccessControlPanelDetails.tsx @@ -3,12 +3,13 @@ import { PropsWithChildren } from 'react'; import _ from 'lodash'; import { Info } from 'lucide-react'; -import { ownershipIcon, truncate } from '@/portainer/filters/filters'; +import { truncate } from '@/portainer/filters/filters'; import { UserId } from '@/portainer/users/types'; import { TeamId } from '@/react/portainer/users/teams/types'; import { useTeams } from '@/react/portainer/users/teams/queries'; import { useUsers } from '@/portainer/users/queries'; import { pluralize } from '@/portainer/helpers/strings'; +import { ownershipIcon } from '@/react/docker/components/datatable/createOwnershipColumn'; import { Link } from '@@/Link'; import { Tooltip } from '@@/Tip/Tooltip'; diff --git a/app/react/portainer/access-control/EditDetails/useOptions.tsx b/app/react/portainer/access-control/EditDetails/useOptions.tsx index 8d455ae65..af2194798 100644 --- a/app/react/portainer/access-control/EditDetails/useOptions.tsx +++ b/app/react/portainer/access-control/EditDetails/useOptions.tsx @@ -2,8 +2,8 @@ import _ from 'lodash'; import { useEffect, useState } from 'react'; import { buildOption } from '@/portainer/components/BoxSelector'; -import { ownershipIcon } from '@/portainer/filters/filters'; import { Team } from '@/react/portainer/users/teams/types'; +import { ownershipIcon } from '@/react/docker/components/datatable/createOwnershipColumn'; import { BoxSelectorOption } from '@@/BoxSelector/types'; import { BadgeIcon } from '@@/BadgeIcon'; @@ -16,7 +16,7 @@ const publicOption: BoxSelectorOption = { id: 'access_public', description: 'I want any user with access to this environment to be able to manage this resource', - icon: , + icon: , }; export function useOptions( @@ -41,14 +41,16 @@ function adminOptions() { return [ buildOption( 'access_administrators', - , + , 'Administrators', 'I want to restrict the management of this resource to administrators only', ResourceControlOwnership.ADMINISTRATORS ), buildOption( 'access_restricted', - , + , 'Restricted', 'I want to restrict the management of this resource to a set of users and/or teams', ResourceControlOwnership.RESTRICTED @@ -59,7 +61,7 @@ function nonAdminOptions(teams?: Team[]) { return _.compact([ buildOption( 'access_private', - , + , 'Private', 'I want to restrict this resource to be manageable by myself only', ResourceControlOwnership.PRIVATE @@ -68,7 +70,7 @@ function nonAdminOptions(teams?: Team[]) { teams.length > 0 && buildOption( 'access_restricted', - , + , 'Restricted', teams.length === 1 ? ( <>