diff --git a/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.html b/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.html deleted file mode 100644 index 522560f91..000000000 --- a/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.html +++ /dev/null @@ -1,209 +0,0 @@ -
- - -
-
-
- -
- - {{ $ctrl.titleText }} - -
- -
- - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - Actions
{{ item.PodName }}{{ item.Name }}{{ item.Image | truncate : 64 }}{{ item.ImagePullPolicy }}{{ item.Status }} - - - {{ item.Node }} - - - - - {{ item.PodIP }}{{ item.CreationDate | getisodate }} - - Stats - - - Logs - - - Console - -
Loading...
No pod available.
-
- -
-
-
diff --git a/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.js b/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.js deleted file mode 100644 index 7e096228b..000000000 --- a/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.js +++ /dev/null @@ -1,14 +0,0 @@ -angular.module('portainer.kubernetes').component('kubernetesContainersDatatable', { - templateUrl: './containersDatatable.html', - controller: 'GenericDatatableController', - bindings: { - titleText: '@', - titleIcon: '@', - dataset: '<', - tableKey: '@', - orderBy: '@', - refreshCallback: '<', - isPod: '<', - useServerMetrics: '<', - }, -}); diff --git a/app/kubernetes/helpers/application/index.js b/app/kubernetes/helpers/application/index.js index 6add57116..d4a0f8d43 100644 --- a/app/kubernetes/helpers/application/index.js +++ b/app/kubernetes/helpers/application/index.js @@ -76,12 +76,6 @@ class KubernetesApplicationHelper { return containers; } - static associateAllContainersAndApplication(app) { - const containers = _.flatMap(_.map(app.Pods, 'Containers')); - KubernetesApplicationHelper.associateContainerPersistedFoldersAndConfigurations(app, containers); - return containers; - } - static portMappingsFromApplications(applications) { const res = _.reduce( applications, diff --git a/app/kubernetes/pod/filters.js b/app/kubernetes/pod/filters.js deleted file mode 100644 index 105b561e2..000000000 --- a/app/kubernetes/pod/filters.js +++ /dev/null @@ -1,52 +0,0 @@ -import _ from 'lodash-es'; - -angular - .module('portainer.kubernetes') - .filter('kubernetesPodStatusColor', function () { - 'use strict'; - return function (text) { - var status = _.toLower(text); - switch (status) { - case 'running': - return 'success'; - case 'waiting': - return 'warning'; - case 'terminated': - return 'info'; - default: - return 'danger'; - } - }; - }) - .filter('kubernetesPodConditionStatusText', function () { - 'use strict'; - return function (status, type) { - switch (type) { - case 'Unschedulable': - switch (status) { - case 'True': - return 'Alert'; - case 'False': - return 'OK'; - case 'Unknown': - return 'Warning'; - } - break; - case 'PodScheduled': - case 'Ready': - case 'Initialized': - case 'ContainersReady': - switch (status) { - case 'True': - return 'Ok'; - case 'False': - return 'Alert'; - case 'Unknown': - return 'Warning'; - } - break; - default: - return 'Unknown'; - } - }; - }); diff --git a/app/kubernetes/react/components/index.ts b/app/kubernetes/react/components/index.ts index 21201c221..1ee99dd0c 100644 --- a/app/kubernetes/react/components/index.ts +++ b/app/kubernetes/react/components/index.ts @@ -17,6 +17,7 @@ import { ApplicationDetailsWidget, ApplicationEventsDatatable, } from '@/react/kubernetes/applications/DetailsView'; +import { ApplicationContainersDatatable } from '@/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable'; import { withFormValidation } from '@/react-tools/withFormValidation'; import { withCurrentUser } from '@/react-tools/withCurrentUser'; import { PlacementsDatatable } from '@/react/kubernetes/applications/ItemView/PlacementsDatatable'; @@ -102,6 +103,15 @@ export const ngModule = angular [] ) ) + .component( + 'applicationContainersDatatable', + r2a( + withUIRouter( + withReactQuery(withCurrentUser(ApplicationContainersDatatable)) + ), + [] + ) + ) .component( 'applicationDetailsWidget', r2a( diff --git a/app/kubernetes/views/applications/edit/application.html b/app/kubernetes/views/applications/edit/application.html index 13057f9aa..f14be606d 100644 --- a/app/kubernetes/views/applications/edit/application.html +++ b/app/kubernetes/views/applications/edit/application.html @@ -78,17 +78,6 @@
-
- - -
+
diff --git a/app/kubernetes/views/applications/edit/applicationController.js b/app/kubernetes/views/applications/edit/applicationController.js index 2eac442e3..2771cf7b9 100644 --- a/app/kubernetes/views/applications/edit/applicationController.js +++ b/app/kubernetes/views/applications/edit/applicationController.js @@ -10,7 +10,6 @@ import { KubernetesDeploymentTypes, } from 'Kubernetes/models/application/models'; import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper'; -import KubernetesApplicationHelper from 'Kubernetes/helpers/application'; import { KubernetesServiceTypes } from 'Kubernetes/models/service/models'; import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models'; import { KubernetesPodContainerTypes } from 'Kubernetes/pod/models/index'; @@ -199,7 +198,6 @@ class KubernetesApplicationController { this.KubernetesNodeService.get(), ]); this.application = application; - this.allContainers = KubernetesApplicationHelper.associateAllContainersAndApplication(application); this.placements = computePlacements(nodes, this.application); this.state.placementWarning = _.find(this.placements, { AcceptsApplication: true }) ? false : true; @@ -238,7 +236,6 @@ class KubernetesApplicationController { eventWarningCount: 0, placementWarning: false, expandedNote: false, - useServerMetrics: this.endpoint.Kubernetes.Configuration.UseServerMetrics, publicUrl: this.endpoint.PublicURL, }; diff --git a/app/react/components/datatables/DatatableContent.tsx b/app/react/components/datatables/DatatableContent.tsx index be71aca09..d435a919a 100644 --- a/app/react/components/datatables/DatatableContent.tsx +++ b/app/react/components/datatables/DatatableContent.tsx @@ -25,7 +25,7 @@ export function DatatableContent>({ const pageRowModel = tableInstance.getPaginationRowModel(); return ( - +
{headerGroups.map((headerGroup) => ( diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/ApplicationContainersDatatable.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/ApplicationContainersDatatable.tsx new file mode 100644 index 000000000..16cf04ae9 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/ApplicationContainersDatatable.tsx @@ -0,0 +1,111 @@ +import { Server } from 'lucide-react'; +import { useCurrentStateAndParams } from '@uirouter/react'; +import { useMemo } from 'react'; +import { ContainerStatus, Pod } from 'kubernetes-types/core/v1'; + +import { IndexOptional } from '@/react/kubernetes/configs/types'; +import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; +import { useEnvironment } from '@/react/portainer/environments/queries'; + +import { Datatable } from '@@/datatables'; +import { useTableState } from '@@/datatables/useTableState'; + +import { useApplication, useApplicationPods } from '../../application.queries'; + +import { ContainerRowData } from './types'; +import { getColumns } from './columns'; + +const storageKey = 'k8sContainersDatatable'; +const settingsStore = createStore(storageKey); + +export function ApplicationContainersDatatable() { + const tableState = useTableState(settingsStore, storageKey); + const { + params: { + endpointId: environmentId, + name, + namespace, + 'resource-type': resourceType, + }, + } = useCurrentStateAndParams(); + + // get the containers from the aapplication pods + const { data: application, ...applicationQuery } = useApplication( + environmentId, + namespace, + name, + resourceType + ); + const { data: pods, ...podsQuery } = useApplicationPods( + environmentId, + namespace, + name, + application + ); + const appContainers = useContainersRowData(pods); + + const { data: isServerMetricsEnabled } = useEnvironment( + environmentId, + (environment) => !!environment?.Kubernetes?.Configuration.UseServerMetrics + ); + + return ( + > + dataset={appContainers} + columns={getColumns(!!isServerMetricsEnabled)} + settingsManager={tableState} + isLoading={applicationQuery.isLoading || podsQuery.isLoading} + emptyContentLabel="No containers found" + title="Application containers" + titleIcon={Server} + getRowId={(row) => row.name} + disableSelect + /> + ); +} + +// useContainersRowData row data gets the pod.spec?.containers and pod.spec?.initContainers from an array of pods +// it then appends the podName, nodeName, podId, creationDate, and status to each container +function useContainersRowData(pods?: Pod[]): ContainerRowData[] { + return ( + useMemo( + () => + pods?.flatMap((pod) => { + const containers = [ + ...(pod.spec?.containers || []), + ...(pod.spec?.initContainers || []), + ]; + return containers.map((container) => ({ + ...container, + podName: pod.metadata?.name ?? '', + nodeName: pod.spec?.nodeName ?? '', + podIp: pod.status?.podIP ?? '', + creationDate: pod.status?.startTime ?? '', + status: computeContainerStatus( + container.name, + pod.status?.containerStatuses + ), + })); + }) || [], + [pods] + ) || [] + ); +} + +function computeContainerStatus( + containerName: string, + statuses?: ContainerStatus[] +) { + const status = statuses?.find((status) => status.name === containerName); + if (!status) { + return 'Terminated'; + } + const { state } = status; + if (state?.waiting) { + return 'Waiting'; + } + if (!state?.running) { + return 'Terminated'; + } + return 'Running'; +} diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/actions.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/actions.tsx new file mode 100644 index 000000000..dcf596170 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/actions.tsx @@ -0,0 +1,49 @@ +import { BarChart, FileText, Terminal } from 'lucide-react'; + +import { Authorized } from '@/react/hooks/useUser'; + +import { Link } from '@@/Link'; +import { Icon } from '@@/Icon'; + +import { columnHelper } from './helper'; + +export function getActions(isServerMetricsEnabled: boolean) { + return columnHelper.accessor(() => '', { + header: 'Actions', + enableSorting: false, + cell: ({ row: { original: container } }) => ( +
+ {container.status === 'Running' && isServerMetricsEnabled && ( + + + Stats + + )} + + + Logs + + {container.status === 'Running' && ( + + + + Console + + + )} +
+ ), + }); +} diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/creationDate.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/creationDate.tsx new file mode 100644 index 000000000..53fb91f29 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/creationDate.tsx @@ -0,0 +1,11 @@ +import { formatDate } from '@/portainer/filters/filters'; + +import { columnHelper } from './helper'; + +export const creationDate = columnHelper.accessor( + (row) => formatDate(row.creationDate), + { + header: 'Creation Date', + cell: ({ getValue }) => getValue(), + } +); diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/helper.ts b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/helper.ts new file mode 100644 index 000000000..aad7ac21e --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/helper.ts @@ -0,0 +1,5 @@ +import { createColumnHelper } from '@tanstack/react-table'; + +import { ContainerRowData } from '../types'; + +export const columnHelper = createColumnHelper(); diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/image.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/image.tsx new file mode 100644 index 000000000..b596c107f --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/image.tsx @@ -0,0 +1,10 @@ +import { columnHelper } from './helper'; + +export const image = columnHelper.accessor('image', { + header: 'Image', + cell: ({ getValue }) => ( +
+ {getValue()} +
+ ), +}); diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/imagePullPolicy.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/imagePullPolicy.tsx new file mode 100644 index 000000000..46451a91c --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/imagePullPolicy.tsx @@ -0,0 +1,6 @@ +import { columnHelper } from './helper'; + +export const imagePullPolicy = columnHelper.accessor('imagePullPolicy', { + header: 'Image Pull Policy', + id: 'imagePullPolicy', +}); diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/index.ts b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/index.ts new file mode 100644 index 000000000..450f08c36 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/index.ts @@ -0,0 +1,23 @@ +import { pod } from './pod'; +import { name } from './name'; +import { image } from './image'; +import { imagePullPolicy } from './imagePullPolicy'; +import { status } from './status'; +import { node } from './node'; +import { podIp } from './podIp'; +import { creationDate } from './creationDate'; +import { getActions } from './actions'; + +export function getColumns(isServerMetricsEnabled: boolean) { + return [ + pod, + name, + image, + imagePullPolicy, + status, + node, + podIp, + creationDate, + getActions(isServerMetricsEnabled), + ]; +} diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/name.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/name.tsx new file mode 100644 index 000000000..db2f4bfdf --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/name.tsx @@ -0,0 +1,6 @@ +import { columnHelper } from './helper'; + +export const name = columnHelper.accessor('name', { + header: 'Name', + id: 'name', +}); diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/node.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/node.tsx new file mode 100644 index 000000000..d0c9064e7 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/node.tsx @@ -0,0 +1,24 @@ +import { Authorized } from '@/react/hooks/useUser'; + +import { Link } from '@@/Link'; + +import { columnHelper } from './helper'; + +export const node = columnHelper.accessor('nodeName', { + header: 'Node', + cell: ({ getValue }) => { + const nodeName = getValue(); + return ( + + +
+ {nodeName} +
+ +
+ ); + }, +}); diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/pod.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/pod.tsx new file mode 100644 index 000000000..c35863c25 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/pod.tsx @@ -0,0 +1,10 @@ +import { columnHelper } from './helper'; + +export const pod = columnHelper.accessor('podName', { + header: 'Pod', + cell: ({ getValue }) => ( +
+ {getValue()} +
+ ), +}); diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/podIp.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/podIp.tsx new file mode 100644 index 000000000..e6fc1c982 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/podIp.tsx @@ -0,0 +1,6 @@ +import { columnHelper } from './helper'; + +export const podIp = columnHelper.accessor('podIp', { + header: 'Pod IP', + id: 'podIp', +}); diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/status.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/status.tsx new file mode 100644 index 000000000..d181f8377 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/columns/status.tsx @@ -0,0 +1,29 @@ +import { CellContext } from '@tanstack/react-table'; + +import { Badge, BadgeType } from '@@/Badge'; + +import { ContainerRowData } from '../types'; + +import { columnHelper } from './helper'; + +export const status = columnHelper.accessor('status', { + header: 'Status', + cell: StatusCell, +}); + +function StatusCell({ getValue }: CellContext) { + return {getValue()}; +} + +function getContainerStatusType(status: string): BadgeType { + switch (status.toLowerCase()) { + case 'running': + return 'success'; + case 'waiting': + return 'warn'; + case 'terminated': + return 'info'; + default: + return 'danger'; + } +} diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/index.ts b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/index.ts new file mode 100644 index 000000000..52b079c09 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/index.ts @@ -0,0 +1 @@ +export { ApplicationContainersDatatable } from './ApplicationContainersDatatable'; diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/types.ts b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/types.ts new file mode 100644 index 000000000..7981ab187 --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable/types.ts @@ -0,0 +1,9 @@ +import { Container } from 'kubernetes-types/core/v1'; + +export interface ContainerRowData extends Container { + podName: string; + nodeName: string; + podIp: string; + creationDate: string; + status: string; +}