diff --git a/app/kubernetes/models/storage-class/StorageClass.ts b/app/kubernetes/models/storage-class/StorageClass.ts new file mode 100644 index 000000000..77fda852d --- /dev/null +++ b/app/kubernetes/models/storage-class/StorageClass.ts @@ -0,0 +1,7 @@ +export class StorageClass { + Name: string = ''; + + Provisioner: string = ''; + + AllowVolumeExpansion: boolean = false; +} diff --git a/app/kubernetes/models/storage-class/models.js b/app/kubernetes/models/storage-class/models.js index dff77f4f0..556f4c8d8 100644 --- a/app/kubernetes/models/storage-class/models.js +++ b/app/kubernetes/models/storage-class/models.js @@ -1,3 +1,5 @@ +export { StorageClass as KubernetesStorageClass } from './StorageClass'; + /** * KubernetesStorageClassAccessPolicies Model */ @@ -17,19 +19,3 @@ const _KubernetesStorageClassAccessPolicies = Object.freeze([ export function KubernetesStorageClassAccessPolicies() { return JSON.parse(JSON.stringify(_KubernetesStorageClassAccessPolicies)); } - -/** - * KubernetesStorageClass Model - */ -const _KubernetesStorageClass = Object.freeze({ - Name: '', - AccessModes: [], - Provisioner: '', - AllowVolumeExpansion: false, -}); - -export class KubernetesStorageClass { - constructor() { - Object.assign(this, JSON.parse(JSON.stringify(_KubernetesStorageClass))); - } -} diff --git a/app/kubernetes/models/volume/PersistentVolumeClaim.ts b/app/kubernetes/models/volume/PersistentVolumeClaim.ts new file mode 100644 index 000000000..5db4e0aee --- /dev/null +++ b/app/kubernetes/models/volume/PersistentVolumeClaim.ts @@ -0,0 +1,33 @@ +import uuidv4 from 'uuid/v4'; + +import { StorageClass } from '../storage-class/StorageClass'; + +export class PersistentVolumeClaim { + Id: string = uuidv4(); + + Name: string = ''; + + PreviousName: string = ''; + + Namespace: string = ''; + + Storage: number = 0; + + storageClass?: StorageClass; // KubernetesStorageClass + + CreationDate: string = ''; + + ApplicationOwner: string = ''; + + AccessModes: Array = []; + + ApplicationName: string = ''; + + /** + * used for Application creation from `ApplicationFormValues` + * not used from API conversion + */ + MountPath: string = ''; + + Yaml: string = ''; +} diff --git a/app/kubernetes/models/volume/Volume.ts b/app/kubernetes/models/volume/Volume.ts new file mode 100644 index 000000000..02c7f2366 --- /dev/null +++ b/app/kubernetes/models/volume/Volume.ts @@ -0,0 +1,14 @@ +import { KubernetesApplication } from '../application/models'; +import { KubernetesResourcePool } from '../resource-pool/models'; + +import { PersistentVolumeClaim } from './PersistentVolumeClaim'; + +type VolumeResourcePool = ReturnType; + +export class Volume { + ResourcePool: VolumeResourcePool = {} as VolumeResourcePool; + + PersistentVolumeClaim: PersistentVolumeClaim = {} as PersistentVolumeClaim; + + Applications: KubernetesApplication[] = []; +} diff --git a/app/kubernetes/models/volume/models.js b/app/kubernetes/models/volume/models.js index 2163412e8..4228cd948 100644 --- a/app/kubernetes/models/volume/models.js +++ b/app/kubernetes/models/volume/models.js @@ -1,40 +1,2 @@ -import uuidv4 from 'uuid/v4'; -/** - * KubernetesPersistentVolumeClaim Model - */ -const _KubernetesPersistentVolumeClaim = Object.freeze({ - Id: '', - Name: '', - PreviousName: '', - Namespace: '', - Storage: 0, - storageClass: {}, // KubernetesStorageClass - CreationDate: '', - ApplicationOwner: '', - AccessModes: [], - ApplicationName: '', - MountPath: '', // used for Application creation from ApplicationFormValues | not used from API conversion - Yaml: '', -}); - -export class KubernetesPersistentVolumeClaim { - constructor() { - Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPersistentVolumeClaim))); - this.Name = uuidv4(); - } -} - -/** - * KubernetesVolume Model (Composite) - */ -const _KubernetesVolume = Object.freeze({ - ResourcePool: {}, // KubernetesResourcePool - PersistentVolumeClaim: {}, // KubernetesPersistentVolumeClaim - Applications: [], // KubernetesApplication -}); - -export class KubernetesVolume { - constructor() { - Object.assign(this, JSON.parse(JSON.stringify(_KubernetesVolume))); - } -} +export { Volume as KubernetesVolume } from './Volume'; +export { PersistentVolumeClaim as KubernetesPersistentVolumeClaim } from './PersistentVolumeClaim'; diff --git a/app/kubernetes/react/components/volumes.ts b/app/kubernetes/react/components/volumes.ts index 8117bbe3a..35a4d0c3c 100644 --- a/app/kubernetes/react/components/volumes.ts +++ b/app/kubernetes/react/components/volumes.ts @@ -1,9 +1,10 @@ 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'; +import { VolumesDatatable } from '@/react/kubernetes/volumes/ListView/VolumesDatatable'; +import { StorageDatatable } from '@/react/kubernetes/volumes/ListView/StorageDatatable'; export const volumesModule = angular .module('portainer.kubernetes.react.components.volumes', []) @@ -14,4 +15,11 @@ export const volumesModule = angular 'onRemove', 'onRefresh', ]) + ) + .component( + 'kubernetesVolumesStoragesDatatable', + r2a(withUIRouter(withCurrentUser(StorageDatatable)), [ + 'dataset', + 'onRefresh', + ]) ).name; diff --git a/app/kubernetes/views/volumes/components/volumes-storages-datatable/controller.js b/app/kubernetes/views/volumes/components/volumes-storages-datatable/controller.js deleted file mode 100644 index 7b5339570..000000000 --- a/app/kubernetes/views/volumes/components/volumes-storages-datatable/controller.js +++ /dev/null @@ -1,79 +0,0 @@ -import _ from 'lodash-es'; - -angular.module('portainer.kubernetes').controller('KubernetesVolumesStoragesDatatableController', [ - '$scope', - '$controller', - 'DatatableService', - function ($scope, $controller, DatatableService) { - angular.extend(this, $controller('GenericDatatableController', { $scope: $scope })); - this.state = Object.assign(this.state, { - expandedItems: [], - expandAll: false, - }); - - this.onSettingsRepeaterChange = function () { - DatatableService.setDataTableSettings(this.tableKey, this.settings); - }; - - this.expandItem = function (item, expanded) { - if (!this.itemCanExpand(item)) { - return; - } - - item.Expanded = expanded; - if (!expanded) { - item.Highlighted = false; - } - }; - - this.itemCanExpand = function (item) { - return item.Volumes.length > 0; - }; - - this.hasExpandableItems = function () { - return _.filter(this.state.filteredDataSet, (item) => this.itemCanExpand(item)).length; - }; - - this.expandAll = function () { - this.state.expandAll = !this.state.expandAll; - _.forEach(this.state.filteredDataSet, (item) => { - if (this.itemCanExpand(item)) { - this.expandItem(item, this.state.expandAll); - } - }); - }; - - 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/kubernetes/views/volumes/components/volumes-storages-datatable/index.js b/app/kubernetes/views/volumes/components/volumes-storages-datatable/index.js deleted file mode 100644 index 8a48e18c2..000000000 --- a/app/kubernetes/views/volumes/components/volumes-storages-datatable/index.js +++ /dev/null @@ -1,13 +0,0 @@ -angular.module('portainer.kubernetes').component('kubernetesVolumesStoragesDatatable', { - templateUrl: './template.html', - controller: 'KubernetesVolumesStoragesDatatableController', - bindings: { - titleText: '@', - titleIcon: '@', - dataset: '<', - tableKey: '@', - orderBy: '@', - reverseOrder: '<', - refreshCallback: '<', - }, -}); diff --git a/app/kubernetes/views/volumes/components/volumes-storages-datatable/template.html b/app/kubernetes/views/volumes/components/volumes-storages-datatable/template.html deleted file mode 100644 index f2851e4a6..000000000 --- a/app/kubernetes/views/volumes/components/volumes-storages-datatable/template.html +++ /dev/null @@ -1,150 +0,0 @@ -
- -
-
-
-
- -
- Storage -
- -
-
- - - - -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
- - - -
-
- - -
-
{{ item.Name }}{{ item.size }}
- - {{ vol.PersistentVolumeClaim.Name }} - - - {{ vol.PersistentVolumeClaim.Storage }} -
Loading...
No storage available.
-
- -
diff --git a/app/kubernetes/views/volumes/volumes.html b/app/kubernetes/views/volumes/volumes.html index 06ca3e0bb..9fd0f27a3 100644 --- a/app/kubernetes/views/volumes/volumes.html +++ b/app/kubernetes/views/volumes/volumes.html @@ -21,8 +21,8 @@ Storage - - + + diff --git a/app/react/kubernetes/volumes/ListView/StorageDatatable.tsx b/app/react/kubernetes/volumes/ListView/StorageDatatable.tsx new file mode 100644 index 000000000..108dc76a0 --- /dev/null +++ b/app/react/kubernetes/volumes/ListView/StorageDatatable.tsx @@ -0,0 +1,95 @@ +import { createColumnHelper } from '@tanstack/react-table'; +import { HardDrive } from 'lucide-react'; + +import { TableSettingsMenu } from '@@/datatables'; +import { + BasicTableSettings, + RefreshableTableSettings, + refreshableSettings, +} from '@@/datatables/types'; +import { useTableStateWithStorage } from '@@/datatables/useTableState'; +import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh'; +import { useRepeater } from '@@/datatables/useRepeater'; +import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable'; +import { buildExpandColumn } from '@@/datatables/expand-column'; +import { Link } from '@@/Link'; + +import { StorageClassViewModel } from './types'; + +interface TableSettings extends BasicTableSettings, RefreshableTableSettings {} + +const helper = createColumnHelper(); + +const columns = [ + buildExpandColumn(), + helper.accessor('Name', { + header: 'Storage', + }), + helper.accessor('size', { + header: 'Usage', + }), +]; + +export function StorageDatatable({ + dataset, + onRefresh, +}: { + dataset: Array; + onRefresh: () => void; +}) { + const tableState = useTableStateWithStorage( + 'kubernetes.volumes.storages', + 'Name', + (set) => ({ + ...refreshableSettings(set), + }) + ); + + useRepeater(tableState.autoRefreshRate, onRefresh); + + return ( + ( + + tableState.setAutoRefreshRate(value)} + /> + + )} + getRowCanExpand={(row) => row.original.Volumes.length > 0} + renderSubRow={(row) => } + /> + ); +} + +function SubRow({ item }: { item: StorageClassViewModel }) { + return ( + <> + {item.Volumes.map((vol) => ( + + + + + {vol.PersistentVolumeClaim.Name} + + + {vol.PersistentVolumeClaim.Storage} + + ))} + + ); +} diff --git a/app/react/kubernetes/volumes/ListView/types.ts b/app/react/kubernetes/volumes/ListView/types.ts index 16a682a8d..5f160c474 100644 --- a/app/react/kubernetes/volumes/ListView/types.ts +++ b/app/react/kubernetes/volumes/ListView/types.ts @@ -1,3 +1,6 @@ +import { StorageClass } from '@/kubernetes/models/storage-class/StorageClass'; +import { Volume } from '@/kubernetes/models/volume/Volume'; + export interface VolumeViewModel { Applications: Array<{ Name: string; @@ -17,3 +20,8 @@ export interface VolumeViewModel { }; }; } + +export type StorageClassViewModel = StorageClass & { + size: 0; + Volumes: Array; +};