diff --git a/app/kubernetes/__module.js b/app/kubernetes/__module.js index 74e07f892..c235645a7 100644 --- a/app/kubernetes/__module.js +++ b/app/kubernetes/__module.js @@ -151,7 +151,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo const application = { name: 'kubernetes.applications.application', - url: '/:namespace/:name', + url: '/:namespace/:name?resource-type', views: { 'content@': { component: 'kubernetesApplicationView', diff --git a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html index 4364851d4..1aa610f59 100644 --- a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html +++ b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html @@ -276,7 +276,7 @@ {{ item.Name }} diff --git a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatableController.js b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatableController.js index 386b495ce..9b484898c 100644 --- a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatableController.js +++ b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatableController.js @@ -33,6 +33,13 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo }, }; + this.applicationTypeEnumToParamMap = { + [KubernetesApplicationTypes.DEPLOYMENT]: 'Deployment', + [KubernetesApplicationTypes.DAEMONSET]: 'DaemonSet', + [KubernetesApplicationTypes.STATEFULSET]: 'StatefulSet', + [KubernetesApplicationTypes.POD]: 'Pod', + }; + this.expandAll = function () { this.state.expandAll = !this.state.expandAll; this.state.filteredDataSet.forEach((item) => this.expandItem(item, this.state.expandAll)); diff --git a/app/kubernetes/react/components/index.ts b/app/kubernetes/react/components/index.ts index 24b291106..44466c181 100644 --- a/app/kubernetes/react/components/index.ts +++ b/app/kubernetes/react/components/index.ts @@ -8,6 +8,10 @@ import { NamespaceAccessUsersSelector } from '@/react/kubernetes/namespaces/Acce import { CreateNamespaceRegistriesSelector } from '@/react/kubernetes/namespaces/CreateView/CreateNamespaceRegistriesSelector'; import { KubeApplicationAccessPolicySelector } from '@/react/kubernetes/applications/CreateView/KubeApplicationAccessPolicySelector'; import { KubeApplicationDeploymentTypeSelector } from '@/react/kubernetes/applications/CreateView/KubeApplicationDeploymentTypeSelector'; +import { ApplicationSummaryWidget } from '@/react/kubernetes/applications/DetailsView'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; +import { withUserProvider } from '@/react/test-utils/withUserProvider'; export const componentsModule = angular .module('portainer.kubernetes.react.components', []) @@ -82,4 +86,11 @@ export const componentsModule = angular 'onChange', 'supportGlobalDeployment', ]) + ) + .component( + 'applicationSummaryWidget', + r2a( + withUIRouter(withReactQuery(withUserProvider(ApplicationSummaryWidget))), + [] + ) ).name; diff --git a/app/kubernetes/views/applications/edit/application.html b/app/kubernetes/views/applications/edit/application.html index 32bd6903e..aa1b1723c 100644 --- a/app/kubernetes/views/applications/edit/application.html +++ b/app/kubernetes/views/applications/edit/application.html @@ -25,133 +25,7 @@ Application -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Name - {{ ctrl.application.Name }} - external -
Stack{{ ctrl.application.StackName || '-' }}
Namespace - {{ ctrl.application.ResourcePool }} - system -
Application Type - {{ ctrl.application.ApplicationType | kubernetesApplicationTypeText }} -
Status - Replicated - Global - {{ ctrl.application.RunningPodsCount }} / - {{ ctrl.application.TotalPodsCount }} - - {{ ctrl.application.Pods[0].Status }} -
-
Resource reservations
-
per instance
-
-
CPU {{ ctrl.application.Requests.Cpu | kubernetesApplicationCPUValue }}
-
Memory {{ ctrl.application.Requests.Memory | humansize }}
-
Creation - - {{ ctrl.application.ApplicationOwner }} - - {{ ctrl.application.CreationDate | getisodate }} - - Deployed from {{ ctrl.state.appType }} - -
-
-
-
- Note - -
-
-
-
- -
-
-
-
- -
-
-
-
-
+
diff --git a/app/react/kubernetes/DashboardView/DashboardView.tsx b/app/react/kubernetes/DashboardView/DashboardView.tsx index 81820efb5..2dcd87b5b 100644 --- a/app/react/kubernetes/DashboardView/DashboardView.tsx +++ b/app/react/kubernetes/DashboardView/DashboardView.tsx @@ -8,7 +8,7 @@ import { DashboardItem } from '@@/DashboardItem/DashboardItem'; import { PageHeader } from '@@/PageHeader'; import { useNamespaces } from '../namespaces/queries'; -import { useApplicationsForCluster } from '../applications/queries'; +import { useApplicationsForCluster } from '../applications/application.queries'; import { useConfigurationsForCluster } from '../configs/queries'; import { usePVCsForCluster } from '../volumes/queries'; diff --git a/app/react/kubernetes/ServicesView/service.ts b/app/react/kubernetes/ServicesView/service.ts index bc07a21d7..ae9dd5658 100644 --- a/app/react/kubernetes/ServicesView/service.ts +++ b/app/react/kubernetes/ServicesView/service.ts @@ -4,6 +4,7 @@ import { compact } from 'lodash'; import { withError } from '@/react-tools/react-query'; import axios, { parseAxiosError } from '@/portainer/services/axios'; import { EnvironmentId } from '@/react/portainer/environments/types'; +import { isFulfilled } from '@/react/utils'; import { getNamespaces } from '../namespaces/service'; @@ -52,12 +53,6 @@ export function useServices(environmentId: EnvironmentId) { ); } -function isFulfilled( - input: PromiseSettledResult -): input is PromiseFulfilledResult { - return input.status === 'fulfilled'; -} - export function useMutationDeleteServices(environmentId: EnvironmentId) { const queryClient = useQueryClient(); return useMutation(deleteServices, { diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationSummaryWidget.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationSummaryWidget.tsx new file mode 100644 index 000000000..bab59611b --- /dev/null +++ b/app/react/kubernetes/applications/DetailsView/ApplicationSummaryWidget.tsx @@ -0,0 +1,289 @@ +import { User, Clock, Edit, ChevronDown, ChevronUp } from 'lucide-react'; +import moment from 'moment'; +import { useState } from 'react'; +import { Pod } from 'kubernetes-types/core/v1'; +import { useCurrentStateAndParams } from '@uirouter/react'; + +import { Authorized } from '@/react/hooks/useUser'; +import { notifyError, notifySuccess } from '@/portainer/services/notifications'; + +import { DetailsTable } from '@@/DetailsTable'; +import { Badge } from '@@/Badge'; +import { Link } from '@@/Link'; +import { Button, LoadingButton } from '@@/buttons'; + +import { isSystemNamespace } from '../../namespaces/utils'; +import { + appStackNameLabel, + appKindToDeploymentTypeMap, + appOwnerLabel, + appDeployMethodLabel, + appNoteAnnotation, +} from '../constants'; +import { + applicationIsKind, + bytesToReadableFormat, + getResourceRequests, + getRunningPods, + getTotalPods, + isExternalApplication, +} from '../utils'; +import { + useApplication, + usePatchApplicationMutation, +} from '../application.queries'; +import { Application } from '../types'; + +export function ApplicationSummaryWidget() { + const stateAndParams = useCurrentStateAndParams(); + const { + params: { + namespace, + name, + 'resource-type': resourceType, + endpointId: environmentId, + }, + } = stateAndParams; + const applicationQuery = useApplication( + environmentId, + namespace, + name, + resourceType + ); + const application = applicationQuery.data; + const systemNamespace = isSystemNamespace(namespace); + const externalApplication = application && isExternalApplication(application); + const applicationRequests = application && getResourceRequests(application); + const applicationOwner = application?.metadata?.labels?.[appOwnerLabel]; + const applicationDeployMethod = getApplicationDeployMethod(application); + const applicationNote = + application?.metadata?.annotations?.[appNoteAnnotation]; + + const [isNoteOpen, setIsNoteOpen] = useState(true); + const [applicationNoteFormValues, setApplicationNoteFormValues] = useState( + applicationNote || '' + ); + const patchApplicationMutation = usePatchApplicationMutation( + environmentId, + namespace, + name + ); + + return ( +
+ + + Name + +
+ {name} + {externalApplication && !systemNamespace && ( + external + )} +
+ + + + Stack + + {application?.metadata?.labels?.[appStackNameLabel] || '-'} + + + + Namespace + +
+ + {namespace} + + {systemNamespace && system} +
+ + + + Application type + {application?.kind || '-'} + + {application?.kind && ( + + Status + {applicationIsKind('Pod', application) && ( + + {application?.status?.phase} + + )} + {!applicationIsKind('Pod', application) && ( + + {appKindToDeploymentTypeMap[application.kind]} + + {getRunningPods(application)} + / {getTotalPods(application)} + + )} + + )} + {(!!applicationRequests?.cpu || !!applicationRequests?.memoryBytes) && ( + + + Resource reservations + {!applicationIsKind('Pod', application) && ( +
per instance
+ )} + + + {!!applicationRequests?.cpu && ( +
+ CPU {applicationRequests.cpu} +
+ )} + {!!applicationRequests?.memoryBytes && ( +
+ Memory{' '} + {bytesToReadableFormat(applicationRequests.memoryBytes)} +
+ )} + + + )} + + Creation + +
+ {applicationOwner && ( + + + {applicationOwner} + + )} + + + {moment(application?.metadata?.creationTimestamp).format( + 'YYYY-MM-DD HH:mm:ss' + )} + + {(!externalApplication || systemNamespace) && ( + + + Deployed from {applicationDeployMethod} + + )} +
+ + + + +
+
+
+ Note + +
+
+ + {isNoteOpen && ( + <> +
+
+