diff --git a/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.html b/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.html deleted file mode 100644 index 1563567a2..000000000 --- a/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.html +++ /dev/null @@ -1,228 +0,0 @@ -
- -
-
-
-
- -
- Volumes -
- -
- - -
-
- - - - - - -
-
-
- - - System resources are hidden, this can be changed in the table settings. - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- - - - - - - - - -
- - - - - {{ item.PersistentVolumeClaim.Name }} - system - external - unused - - {{ item.ResourcePool.Namespace.Name }} - - {{ - item.Applications[0].Name - }} - + {{ item.Applications.length - 1 }} - - - - {{ item.PersistentVolumeClaim.storageClass.Name }} - - {{ item.PersistentVolumeClaim.Storage }} - - {{ item.PersistentVolumeClaim.CreationDate | getisodate }} - {{ item.PersistentVolumeClaim.ApplicationOwner ? 'by ' + item.PersistentVolumeClaim.ApplicationOwner : '' }} -
Loading...
No volume available.
-
- -
diff --git a/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.js b/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.js deleted file mode 100644 index ddbdacf7f..000000000 --- a/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.js +++ /dev/null @@ -1,14 +0,0 @@ -angular.module('portainer.kubernetes').component('kubernetesVolumesDatatable', { - templateUrl: './volumesDatatable.html', - controller: 'KubernetesVolumesDatatableController', - bindings: { - titleText: '@', - titleIcon: '@', - dataset: '<', - tableKey: '@', - orderBy: '@', - reverseOrder: '<', - removeAction: '<', - refreshCallback: '<', - }, -}); diff --git a/app/kubernetes/components/datatables/volumes-datatable/volumesDatatableController.js b/app/kubernetes/components/datatables/volumes-datatable/volumesDatatableController.js deleted file mode 100644 index 528fbb6bd..000000000 --- a/app/kubernetes/components/datatables/volumes-datatable/volumesDatatableController.js +++ /dev/null @@ -1,90 +0,0 @@ -import angular from 'angular'; -import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper'; -import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper'; - -// TODO: review - refactor to use `extends GenericDatatableController` -class KubernetesVolumesDatatableController { - /* @ngInject */ - constructor($async, $controller, Authentication, DatatableService) { - this.$async = $async; - this.$controller = $controller; - this.Authentication = Authentication; - this.DatatableService = DatatableService; - - this.onInit = this.onInit.bind(this); - this.allowSelection = this.allowSelection.bind(this); - this.isDisplayed = this.isDisplayed.bind(this); - } - - onSettingsShowSystemChange() { - this.DatatableService.setDataTableSettings(this.tableKey, this.settings); - } - - disableRemove(item) { - return this.isSystemNamespace(item) || this.isUsed(item); - } - - isUsed(item) { - return KubernetesVolumeHelper.isUsed(item); - } - - isSystemNamespace(item) { - return KubernetesNamespaceHelper.isSystemNamespace(item.ResourcePool.Namespace.Name); - } - - isDisplayed(item) { - return !this.isSystemNamespace(item) || this.settings.showSystem; - } - - isExternalVolume(item) { - return KubernetesVolumeHelper.isExternalVolume(item); - } - - allowSelection(item) { - return !this.disableRemove(item); - } - - async onInit() { - this.setDefaults(); - this.prepareTableFromDataset(); - this.isAdmin = this.Authentication.isAdmin(); - this.settings.showSystem = false; - - this.state.orderBy = this.orderBy; - var storedOrder = this.DatatableService.getDataTableOrder(this.tableKey); - if (storedOrder !== null) { - this.state.reverseOrder = storedOrder.reverse; - this.state.orderBy = storedOrder.orderBy; - } - - var textFilter = this.DatatableService.getDataTableTextFilters(this.tableKey); - if (textFilter !== null) { - this.state.textFilter = textFilter; - this.onTextFilterChange(); - } - - var storedFilters = this.DatatableService.getDataTableFilters(this.tableKey); - if (storedFilters !== null) { - this.filters = storedFilters; - } - if (this.filters && this.filters.state) { - this.filters.state.open = false; - } - - var storedSettings = this.DatatableService.getDataTableSettings(this.tableKey); - if (storedSettings !== null) { - this.settings = storedSettings; - this.settings.open = false; - } - this.onSettingsRepeaterChange(); - } - - $onInit() { - const ctrl = angular.extend({}, this.$controller('GenericDatatableController'), this); - angular.extend(this, ctrl); - return this.$async(this.onInit); - } -} - -export default KubernetesVolumesDatatableController; -angular.module('portainer.kubernetes').controller('KubernetesVolumesDatatableController', KubernetesVolumesDatatableController); diff --git a/app/kubernetes/react/components/index.ts b/app/kubernetes/react/components/index.ts index 94e8f9394..1af275ac2 100644 --- a/app/kubernetes/react/components/index.ts +++ b/app/kubernetes/react/components/index.ts @@ -62,9 +62,13 @@ import { kubeEnvVarValidationSchema } from '@/react/kubernetes/applications/comp import { HelmInsightsBox } from '@/react/kubernetes/applications/ListView/ApplicationsDatatable/HelmInsightsBox'; import { applicationsModule } from './applications'; +import { volumesModule } from './volumes'; export const ngModule = angular - .module('portainer.kubernetes.react.components', [applicationsModule]) + .module('portainer.kubernetes.react.components', [ + applicationsModule, + volumesModule, + ]) .component( 'ingressClassDatatable', r2a(IngressClassDatatableAngular, [ diff --git a/app/kubernetes/react/components/volumes.ts b/app/kubernetes/react/components/volumes.ts new file mode 100644 index 000000000..8117bbe3a --- /dev/null +++ b/app/kubernetes/react/components/volumes.ts @@ -0,0 +1,17 @@ +import angular from 'angular'; + +import { r2a } from '@/react-tools/react2angular'; +import { VolumesDatatable } from '@/react/kubernetes/volumes/ListView/VolumesDatatable'; +import { withUIRouter } from '@/react-tools/withUIRouter'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; + +export const volumesModule = angular + .module('portainer.kubernetes.react.components.volumes', []) + .component( + 'kubernetesVolumesDatatable', + r2a(withUIRouter(withCurrentUser(VolumesDatatable)), [ + 'dataset', + 'onRemove', + 'onRefresh', + ]) + ).name; diff --git a/app/kubernetes/views/volumes/volumes.html b/app/kubernetes/views/volumes/volumes.html index 62aec00f7..06ca3e0bb 100644 --- a/app/kubernetes/views/volumes/volumes.html +++ b/app/kubernetes/views/volumes/volumes.html @@ -13,15 +13,8 @@ Volumes - - + + diff --git a/app/kubernetes/views/volumes/volumesController.js b/app/kubernetes/views/volumes/volumesController.js index b21062a6d..a93e757a2 100644 --- a/app/kubernetes/views/volumes/volumesController.js +++ b/app/kubernetes/views/volumes/volumesController.js @@ -3,7 +3,6 @@ import filesizeParser from 'filesize-parser'; import angular from 'angular'; import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper'; import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper'; -import { confirmDelete } from '@@/modals/confirm'; function buildStorages(storages, volumes) { _.forEach(storages, (s) => { @@ -36,37 +35,30 @@ class KubernetesVolumesController { this.getVolumes = this.getVolumes.bind(this); this.getVolumesAsync = this.getVolumesAsync.bind(this); this.removeAction = this.removeAction.bind(this); - this.removeActionAsync = this.removeActionAsync.bind(this); } selectTab(index) { this.LocalStorage.storeActiveTab('volumes', index); } - async removeActionAsync(selectedItems) { - let actionCount = selectedItems.length; - for (const volume of selectedItems) { - try { - await this.KubernetesVolumeService.delete(volume); - this.Notifications.success('Volume successfully removed', volume.PersistentVolumeClaim.Name); - const index = this.volumes.indexOf(volume); - this.volumes.splice(index, 1); - } catch (err) { - this.Notifications.error('Failure', err, 'Unable to remove volume'); - } finally { - --actionCount; - if (actionCount === 0) { - this.$state.reload(this.$state.current); + async removeAction(selectedItems) { + return this.$async(async () => { + let actionCount = selectedItems.length; + for (const volume of selectedItems) { + try { + await this.KubernetesVolumeService.delete(volume); + this.Notifications.success('Volume successfully removed', volume.PersistentVolumeClaim.Name); + const index = this.volumes.indexOf(volume); + this.volumes.splice(index, 1); + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to remove volume'); + } finally { + --actionCount; + if (actionCount === 0) { + this.$state.reload(this.$state.current); + } } } - } - } - - removeAction(selectedItems) { - confirmDelete('Do you want to remove the selected volume(s)?').then((confirmed) => { - if (confirmed) { - return this.$async(this.removeActionAsync, selectedItems); - } }); } diff --git a/app/react/components/Badge/Badge.stories.tsx b/app/react/components/Badge/Badge.stories.tsx index 60824c333..41f42cbc8 100644 --- a/app/react/components/Badge/Badge.stories.tsx +++ b/app/react/components/Badge/Badge.stories.tsx @@ -9,7 +9,16 @@ export default { type: { control: { type: 'select', - options: ['success', 'danger', 'warn', 'info'], + options: [ + 'success', + 'danger', + 'warn', + 'info', + 'successSecondary', + 'dangerSecondary', + 'warnSecondary', + 'infoSecondary', + ], }, }, }, diff --git a/app/react/components/Badge/Badge.tsx b/app/react/components/Badge/Badge.tsx index d7d4df4c9..e31e3b122 100644 --- a/app/react/components/Badge/Badge.tsx +++ b/app/react/components/Badge/Badge.tsx @@ -14,45 +14,45 @@ export type BadgeType = // the classes are typed in full because tailwind doesn't render the interpolated classes const typeClasses: Record = { success: clsx( - `text-success-9 bg-success-2`, - `th-dark:text-success-3 th-dark:bg-success-10`, - `th-highcontrast:text-success-3 th-highcontrast:bg-success-10` + 'text-success-9 bg-success-2', + 'th-dark:text-success-3 th-dark:bg-success-10', + 'th-highcontrast:text-success-3 th-highcontrast:bg-success-10' ), warn: clsx( - `text-warning-9 bg-warning-2`, - `th-dark:text-warning-3 th-dark:bg-warning-10`, - `th-highcontrast:text-warning-3 th-highcontrast:bg-warning-10` + 'text-warning-9 bg-warning-2', + 'th-dark:text-warning-3 th-dark:bg-warning-10', + 'th-highcontrast:text-warning-3 th-highcontrast:bg-warning-10' ), danger: clsx( - `text-error-9 bg-error-2`, - `th-dark:text-error-3 th-dark:bg-error-10`, - `th-highcontrast:text-error-3 th-highcontrast:bg-error-10` + 'text-error-9 bg-error-2', + 'th-dark:text-error-3 th-dark:bg-error-10', + 'th-highcontrast:text-error-3 th-highcontrast:bg-error-10' ), info: clsx( - `text-blue-9 bg-blue-2`, - `th-dark:text-blue-3 th-dark:bg-blue-10`, - `th-highcontrast:text-blue-3 th-highcontrast:bg-blue-10` + 'text-blue-9 bg-blue-2', + 'th-dark:text-blue-3 th-dark:bg-blue-10', + 'th-highcontrast:text-blue-3 th-highcontrast:bg-blue-10' ), // the secondary classes are a bit darker in light mode and a bit lighter in dark mode successSecondary: clsx( - `text-success-9 bg-success-3`, - `th-dark:text-success-3 th-dark:bg-success-9`, - `th-highcontrast:text-success-3 th-highcontrast:bg-success-9` + 'text-success-9 bg-success-3', + 'th-dark:text-success-3 th-dark:bg-success-9', + 'th-highcontrast:text-success-3 th-highcontrast:bg-success-9' ), warnSecondary: clsx( - `text-warning-9 bg-warning-3`, - `th-dark:text-warning-3 th-dark:bg-warning-9`, - `th-highcontrast:text-warning-3 th-highcontrast:bg-warning-9` + 'text-warning-9 bg-warning-3', + 'th-dark:text-warning-3 th-dark:bg-warning-9', + 'th-highcontrast:text-warning-3 th-highcontrast:bg-warning-9' ), dangerSecondary: clsx( - `text-error-9 bg-error-3`, - `th-dark:text-error-3 th-dark:bg-error-9`, - `th-highcontrast:text-error-3 th-highcontrast:bg-error-9` + 'text-error-9 bg-error-3', + 'th-dark:text-error-3 th-dark:bg-error-9', + 'th-highcontrast:text-error-3 th-highcontrast:bg-error-9' ), infoSecondary: clsx( - `text-blue-9 bg-blue-3`, - `th-dark:text-blue-3 th-dark:bg-blue-9`, - `th-highcontrast:text-blue-3 th-highcontrast:bg-blue-9` + 'text-blue-9 bg-blue-3', + 'th-dark:text-blue-3 th-dark:bg-blue-9', + 'th-highcontrast:text-blue-3 th-highcontrast:bg-blue-9' ), }; diff --git a/app/react/components/Badge/ExternalBadge.tsx b/app/react/components/Badge/ExternalBadge.tsx new file mode 100644 index 000000000..cba18f3f1 --- /dev/null +++ b/app/react/components/Badge/ExternalBadge.tsx @@ -0,0 +1,5 @@ +import { Badge } from '@@/Badge'; + +export function ExternalBadge() { + return external; +} diff --git a/app/react/components/Badge/SystemBadge.tsx b/app/react/components/Badge/SystemBadge.tsx new file mode 100644 index 000000000..17552d755 --- /dev/null +++ b/app/react/components/Badge/SystemBadge.tsx @@ -0,0 +1,5 @@ +import { Badge } from '@@/Badge'; + +export function SystemBadge() { + return system; +} diff --git a/app/react/components/Badge/UnusedBadge.tsx b/app/react/components/Badge/UnusedBadge.tsx new file mode 100644 index 000000000..9479d5014 --- /dev/null +++ b/app/react/components/Badge/UnusedBadge.tsx @@ -0,0 +1,5 @@ +import { Badge } from '@@/Badge'; + +export function UnusedBadge() { + return unused; +} diff --git a/app/react/components/PaginationControls/ItemsPerPageSelector.tsx b/app/react/components/PaginationControls/ItemsPerPageSelector.tsx index 8fdbacaba..744db42c2 100644 --- a/app/react/components/PaginationControls/ItemsPerPageSelector.tsx +++ b/app/react/components/PaginationControls/ItemsPerPageSelector.tsx @@ -1,3 +1,6 @@ +import _ from 'lodash'; +import { useState } from 'react'; + interface Props { value: number; onChange(value: number): void; @@ -5,10 +8,17 @@ interface Props { } export function ItemsPerPageSelector({ value, onChange, showAll }: Props) { + const [id] = useState(() => `${_.uniqueId()}-items-per-page`); return ( - Items per page +