diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationDetailsWidget.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationDetailsWidget.tsx index e3fd4a089..46ce98d90 100644 --- a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationDetailsWidget.tsx +++ b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationDetailsWidget.tsx @@ -4,6 +4,7 @@ import { Pod } from 'kubernetes-types/core/v1'; import { Authorized } from '@/react/hooks/useUser'; import { useStackFile } from '@/react/common/stacks/stack.service'; +import { useNamespaceQuery } from '@/react/kubernetes/namespaces/queries/useNamespaceQuery'; import { Widget, WidgetBody } from '@@/Widget'; import { Button } from '@@/buttons'; @@ -14,7 +15,6 @@ import { useApplication, useApplicationServices, } from '../../application.queries'; -import { isSystemNamespace } from '../../../namespaces/utils'; import { applicationIsKind, isExternalApplication } from '../../utils'; import { appStackIdLabel } from '../../constants'; @@ -39,6 +39,9 @@ export function ApplicationDetailsWidget() { }, } = stateAndParams; + const namespaceData = useNamespaceQuery(environmentId, namespace); + const isSystemNamespace = namespaceData.data?.IsSystem; + // get app info const { data: app } = useApplication( environmentId, @@ -61,7 +64,7 @@ export function ApplicationDetailsWidget() {
- {!isSystemNamespace(namespace) && ( + {!isSystemNamespace && (
diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationSummaryWidget.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationSummaryWidget.tsx index 44fddd4c2..77eb82e22 100644 --- a/app/react/kubernetes/applications/DetailsView/ApplicationSummaryWidget.tsx +++ b/app/react/kubernetes/applications/DetailsView/ApplicationSummaryWidget.tsx @@ -18,7 +18,6 @@ import { InlineLoader } from '@@/InlineLoader'; import { Icon } from '@@/Icon'; import { Note } from '@@/Note'; -import { isSystemNamespace } from '../../namespaces/utils'; import { appStackNameLabel, appKindToDeploymentTypeMap, @@ -39,6 +38,7 @@ import { usePatchApplicationMutation, } from '../application.queries'; import { Application, ApplicationPatch } from '../types'; +import { useNamespaceQuery } from '../../namespaces/queries/useNamespaceQuery'; export function ApplicationSummaryWidget() { const stateAndParams = useCurrentStateAndParams(); @@ -56,7 +56,9 @@ export function ApplicationSummaryWidget() { name, resourceType ); - const systemNamespace = isSystemNamespace(namespace); + const namespaceData = useNamespaceQuery(environmentId, namespace); + const isSystemNamespace = namespaceData.data?.IsSystem; + const externalApplication = application && isExternalApplication(application); const applicationRequests = application && getResourceRequests(application); const applicationOwner = application?.metadata?.labels?.[appOwnerLabel]; @@ -124,7 +126,7 @@ export function ApplicationSummaryWidget() { data-cy="k8sAppDetail-appName" > {name} - {externalApplication && !systemNamespace && ( + {externalApplication && !isSystemNamespace && ( external )}
@@ -154,7 +156,7 @@ export function ApplicationSummaryWidget() { > {namespace} - {systemNamespace && system} + {isSystemNamespace && system}
@@ -231,7 +233,7 @@ export function ApplicationSummaryWidget() { application?.metadata?.creationTimestamp ).format('YYYY-MM-DD HH:mm:ss')} - {(!externalApplication || systemNamespace) && ( + {(!externalApplication || isSystemNamespace) && ( (canAccessSystemResources && tableState.showSystemResources) || - !isSystemNamespace(configMap.metadata?.namespace ?? '') + !namespaces?.[configMap.metadata?.namespace ?? '']?.IsSystem ) || [], - [configMaps, tableState, canAccessSystemResources] + [configMaps, tableState, canAccessSystemResources, namespaces] ); const configMapRowData = useConfigMapRowData( filteredConfigMaps, applications ?? [], - applicationsQuery.isLoading + applicationsQuery.isLoading, + namespaces ); return ( @@ -85,7 +86,7 @@ export function ConfigMapsDatatable() { titleIcon={FileCode} getRowId={(row) => row.metadata?.uid ?? ''} isRowSelectable={(row) => - !isSystemNamespace(row.original.metadata?.namespace ?? '') + !namespaces?.[row.original.metadata?.namespace ?? ''].IsSystem } disableSelect={readOnly} renderTableActions={(selectedRows) => ( @@ -110,7 +111,8 @@ export function ConfigMapsDatatable() { function useConfigMapRowData( configMaps: ConfigMap[], applications: Application[], - applicationsLoading: boolean + applicationsLoading: boolean, + namespaces?: Namespaces ): ConfigMapRowData[] { return useMemo( () => @@ -119,8 +121,11 @@ function useConfigMapRowData( inUse: // if the apps are loading, set inUse to true to hide the 'unused' badge applicationsLoading || getIsConfigMapInUse(configMap, applications), + isSystem: namespaces + ? namespaces?.[configMap.metadata?.namespace ?? '']?.IsSystem + : false, })), - [configMaps, applicationsLoading, applications] + [configMaps, applicationsLoading, applications, namespaces] ); } diff --git a/app/react/kubernetes/configs/ListView/ConfigMapsDatatable/columns/name.tsx b/app/react/kubernetes/configs/ListView/ConfigMapsDatatable/columns/name.tsx index 163cf3343..88d707343 100644 --- a/app/react/kubernetes/configs/ListView/ConfigMapsDatatable/columns/name.tsx +++ b/app/react/kubernetes/configs/ListView/ConfigMapsDatatable/columns/name.tsx @@ -1,6 +1,5 @@ import { CellContext } from '@tanstack/react-table'; -import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils'; import { Authorized } from '@/react/hooks/useUser'; import { Link } from '@@/Link'; @@ -13,13 +12,9 @@ import { columnHelper } from './helper'; export const name = columnHelper.accessor( (row) => { const name = row.metadata?.name; - const namespace = row.metadata?.namespace; const isSystemToken = name?.includes('default-token-'); - const isInSystemNamespace = namespace - ? isSystemNamespace(namespace) - : false; - const isSystemConfigMap = isSystemToken || isInSystemNamespace; + const isSystemConfigMap = isSystemToken || row.isSystem; const hasConfigurationOwner = !!row.metadata?.labels?.['io.portainer.kubernetes.configuration.owner']; @@ -36,11 +31,9 @@ export const name = columnHelper.accessor( function Cell({ row }: CellContext) { const name = row.original.metadata?.name; - const namespace = row.original.metadata?.namespace; const isSystemToken = name?.includes('default-token-'); - const isInSystemNamespace = namespace ? isSystemNamespace(namespace) : false; - const isSystemConfigMap = isSystemToken || isInSystemNamespace; + const isSystemConfigMap = isSystemToken || row.original.isSystem; const hasConfigurationOwner = !!row.original.metadata?.labels?.[ diff --git a/app/react/kubernetes/configs/ListView/ConfigMapsDatatable/types.ts b/app/react/kubernetes/configs/ListView/ConfigMapsDatatable/types.ts index 81eabea3e..056f7dc12 100644 --- a/app/react/kubernetes/configs/ListView/ConfigMapsDatatable/types.ts +++ b/app/react/kubernetes/configs/ListView/ConfigMapsDatatable/types.ts @@ -2,4 +2,5 @@ import { ConfigMap } from 'kubernetes-types/core/v1'; export interface ConfigMapRowData extends ConfigMap { inUse: boolean; + isSystem: boolean; } diff --git a/app/react/kubernetes/configs/ListView/SecretsDatatable/SecretsDatatable.tsx b/app/react/kubernetes/configs/ListView/SecretsDatatable/SecretsDatatable.tsx index 4ea8c6e0b..df939d561 100644 --- a/app/react/kubernetes/configs/ListView/SecretsDatatable/SecretsDatatable.tsx +++ b/app/react/kubernetes/configs/ListView/SecretsDatatable/SecretsDatatable.tsx @@ -6,12 +6,12 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { Authorized, useAuthorizations } from '@/react/hooks/useUser'; import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; -import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils'; import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription'; import { useApplicationsQuery } from '@/react/kubernetes/applications/application.queries'; import { Application } from '@/react/kubernetes/applications/types'; import { pluralize } from '@/portainer/helpers/strings'; import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery'; +import { Namespaces } from '@/react/kubernetes/namespaces/types'; import { Datatable, TableSettingsMenu } from '@@/datatables'; import { confirmDelete } from '@@/modals/confirm'; @@ -64,14 +64,15 @@ export function SecretsDatatable() { secrets?.filter( (secret) => (canAccessSystemResources && tableState.showSystemResources) || - !isSystemNamespace(secret.metadata?.namespace ?? '') + !namespaces?.[secret.metadata?.namespace ?? '']?.IsSystem ) || [], - [secrets, tableState, canAccessSystemResources] + [secrets, tableState, canAccessSystemResources, namespaces] ); const secretRowData = useSecretRowData( filteredSecrets, applications ?? [], - applicationsQuery.isLoading + applicationsQuery.isLoading, + namespaces ); return ( @@ -85,7 +86,7 @@ export function SecretsDatatable() { titleIcon={Lock} getRowId={(row) => row.metadata?.uid ?? ''} isRowSelectable={(row) => - !isSystemNamespace(row.original.metadata?.namespace ?? '') + !namespaces?.[row.original.metadata?.namespace ?? '']?.IsSystem } disableSelect={readOnly} renderTableActions={(selectedRows) => ( @@ -110,7 +111,8 @@ export function SecretsDatatable() { function useSecretRowData( secrets: Secret[], applications: Application[], - applicationsLoading: boolean + applicationsLoading: boolean, + namespaces?: Namespaces ): SecretRowData[] { return useMemo( () => @@ -119,8 +121,11 @@ function useSecretRowData( inUse: // if the apps are loading, set inUse to true to hide the 'unused' badge applicationsLoading || getIsSecretInUse(secret, applications), + isSystem: namespaces + ? namespaces?.[secret.metadata?.namespace ?? '']?.IsSystem + : false, })), - [secrets, applicationsLoading, applications] + [secrets, applicationsLoading, applications, namespaces] ); } diff --git a/app/react/kubernetes/configs/ListView/SecretsDatatable/columns/name.tsx b/app/react/kubernetes/configs/ListView/SecretsDatatable/columns/name.tsx index d9ea8db06..ae11acddb 100644 --- a/app/react/kubernetes/configs/ListView/SecretsDatatable/columns/name.tsx +++ b/app/react/kubernetes/configs/ListView/SecretsDatatable/columns/name.tsx @@ -1,6 +1,5 @@ import { CellContext } from '@tanstack/react-table'; -import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils'; import { Authorized } from '@/react/hooks/useUser'; import { Link } from '@@/Link'; @@ -13,16 +12,11 @@ import { columnHelper } from './helper'; export const name = columnHelper.accessor( (row) => { const name = row.metadata?.name; - const namespace = row.metadata?.namespace; - const isSystemToken = name?.includes('default-token-'); - const isInSystemNamespace = namespace - ? isSystemNamespace(namespace) - : false; + const isRegistrySecret = row.metadata?.annotations?.['portainer.io/registry.id']; - const isSystemSecret = - isSystemToken || isInSystemNamespace || isRegistrySecret; + const isSystemSecret = isSystemToken || row.isSystem || isRegistrySecret; const hasConfigurationOwner = !!row.metadata?.labels?.['io.portainer.kubernetes.configuration.owner']; @@ -39,11 +33,9 @@ export const name = columnHelper.accessor( function Cell({ row }: CellContext) { const name = row.original.metadata?.name; - const namespace = row.original.metadata?.namespace; const isSystemToken = name?.includes('default-token-'); - const isInSystemNamespace = namespace ? isSystemNamespace(namespace) : false; - const isSystemSecret = isSystemToken || isInSystemNamespace; + const isSystemSecret = isSystemToken || row.original.isSystem; const hasConfigurationOwner = !!row.original.metadata?.labels?.[ diff --git a/app/react/kubernetes/configs/ListView/SecretsDatatable/types.ts b/app/react/kubernetes/configs/ListView/SecretsDatatable/types.ts index cedb9013d..94a4688b1 100644 --- a/app/react/kubernetes/configs/ListView/SecretsDatatable/types.ts +++ b/app/react/kubernetes/configs/ListView/SecretsDatatable/types.ts @@ -2,4 +2,5 @@ import { Secret } from 'kubernetes-types/core/v1'; export interface SecretRowData extends Secret { inUse: boolean; + isSystem: boolean; } diff --git a/app/react/kubernetes/ingresses/IngressDatatable/IngressDatatable.tsx b/app/react/kubernetes/ingresses/IngressDatatable/IngressDatatable.tsx index bfdad6935..925116f40 100644 --- a/app/react/kubernetes/ingresses/IngressDatatable/IngressDatatable.tsx +++ b/app/react/kubernetes/ingresses/IngressDatatable/IngressDatatable.tsx @@ -7,7 +7,6 @@ import { useAuthorizations, Authorized } from '@/react/hooks/useUser'; import Route from '@/assets/ico/route.svg?c'; import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; -import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils'; import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription'; import { confirmDelete } from '@@/modals/confirm'; @@ -19,6 +18,7 @@ import { useTableState } from '@@/datatables/useTableState'; import { DeleteIngressesRequest, Ingress } from '../types'; import { useDeleteIngresses, useIngresses } from '../queries'; import { useNamespacesQuery } from '../../namespaces/queries/useNamespacesQuery'; +import { Namespaces } from '../../namespaces/types'; import { columns } from './columns'; @@ -54,9 +54,14 @@ export function IngressDatatable() { ingresses?.filter( (ingress) => (canAccessSystemResources && tableState.showSystemResources) || - !isSystemNamespace(ingress.Namespace ?? '') + !namespaces?.[ingress.Namespace].IsSystem ) || [], - [ingresses, tableState, canAccessSystemResources] + [ingresses, tableState, canAccessSystemResources, namespaces] + ); + + const ingressesWithIsSystem = useIngressesRowData( + filteredIngresses || [], + namespaces ); const deleteIngressesMutation = useDeleteIngresses(); @@ -66,13 +71,14 @@ export function IngressDatatable() { return ( row.Name + row.Type + row.Namespace} + isRowSelectable={(row) => !namespaces?.[row.original.Namespace].IsSystem} renderTableActions={tableActions} renderTableSettings={() => ( @@ -88,6 +94,21 @@ export function IngressDatatable() { /> ); + // useIngressesRowData appends the `isSyetem` property to the service data + function useIngressesRowData( + ingresses: Ingress[], + namespaces?: Namespaces + ): Ingress[] { + return useMemo( + () => + ingresses.map((r) => ({ + ...r, + IsSystem: namespaces ? namespaces?.[r.Namespace].IsSystem : false, + })), + [ingresses, namespaces] + ); + } + function tableActions(selectedFlatRows: Ingress[]) { return (
diff --git a/app/react/kubernetes/ingresses/IngressDatatable/columns/name.tsx b/app/react/kubernetes/ingresses/IngressDatatable/columns/name.tsx index f5f7a9e43..5e96da37c 100644 --- a/app/react/kubernetes/ingresses/IngressDatatable/columns/name.tsx +++ b/app/react/kubernetes/ingresses/IngressDatatable/columns/name.tsx @@ -1,7 +1,6 @@ import { CellContext } from '@tanstack/react-table'; import { Authorized } from '@/react/hooks/useUser'; -import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils'; import { Link } from '@@/Link'; import { Badge } from '@@/Badge'; @@ -19,7 +18,6 @@ export const name = columnHelper.accessor('Name', { function Cell({ row, getValue }: CellContext) { const name = getValue(); const namespace = row.original.Namespace; - const isSystemIngress = isSystemNamespace(namespace); return (
@@ -36,7 +34,7 @@ function Cell({ row, getValue }: CellContext) { {name} - {isSystemIngress && ( + {row.original.IsSystem && ( System diff --git a/app/react/kubernetes/ingresses/types.ts b/app/react/kubernetes/ingresses/types.ts index 48445dbc3..8e9b43484 100644 --- a/app/react/kubernetes/ingresses/types.ts +++ b/app/react/kubernetes/ingresses/types.ts @@ -35,6 +35,8 @@ export type Ingress = { Type?: string; Labels?: Record; CreationDate?: string; + + IsSystem?: boolean; }; export interface DeleteIngressesRequest { diff --git a/app/react/kubernetes/namespaces/queries/useNamespaceQuery.ts b/app/react/kubernetes/namespaces/queries/useNamespaceQuery.ts index f9f115b5f..9e158d870 100644 --- a/app/react/kubernetes/namespaces/queries/useNamespaceQuery.ts +++ b/app/react/kubernetes/namespaces/queries/useNamespaceQuery.ts @@ -4,7 +4,7 @@ import axios, { parseAxiosError } from '@/portainer/services/axios'; import { notifyError } from '@/portainer/services/notifications'; import { EnvironmentId } from '@/react/portainer/environments/types'; -import { Namespaces } from '../types'; +import { DefaultOrSystemNamespace } from '../types'; export function useNamespaceQuery( environmentId: EnvironmentId, @@ -27,7 +27,7 @@ export async function getNamespace( namespace: string ) { try { - const { data: ns } = await axios.get( + const { data: ns } = await axios.get( `kubernetes/${environmentId}/namespaces/${namespace}` ); return ns; diff --git a/app/react/kubernetes/namespaces/types.ts b/app/react/kubernetes/namespaces/types.ts index 20436c306..9ec305647 100644 --- a/app/react/kubernetes/namespaces/types.ts +++ b/app/react/kubernetes/namespaces/types.ts @@ -1,6 +1,8 @@ export interface Namespaces { - [key: string]: { - IsDefault: boolean; - IsSystem: boolean; - }; + [key: string]: DefaultOrSystemNamespace; +} + +export interface DefaultOrSystemNamespace { + IsDefault: boolean; + IsSystem: boolean; } diff --git a/app/react/kubernetes/namespaces/utils.ts b/app/react/kubernetes/namespaces/utils.ts deleted file mode 100644 index 92736720b..000000000 --- a/app/react/kubernetes/namespaces/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const systemNamespaces = [ - 'kube-system', - 'kube-public', - 'kube-node-lease', - 'portainer', -]; - -export function isSystemNamespace(namespace: string) { - return systemNamespaces.includes(namespace || ''); -} diff --git a/app/react/kubernetes/services/ServicesView/ServicesDatatable/ServicesDatatable.tsx b/app/react/kubernetes/services/ServicesView/ServicesDatatable/ServicesDatatable.tsx index 21eea4d5a..25ad14305 100644 --- a/app/react/kubernetes/services/ServicesView/ServicesDatatable/ServicesDatatable.tsx +++ b/app/react/kubernetes/services/ServicesView/ServicesDatatable/ServicesDatatable.tsx @@ -1,14 +1,15 @@ +import { useMemo } from 'react'; import { Shuffle, Trash2 } from 'lucide-react'; import { useRouter } from '@uirouter/react'; import clsx from 'clsx'; import { Row } from '@tanstack/react-table'; +import { Namespaces } from '@/react/kubernetes/namespaces/types'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { Authorized, useAuthorizations } from '@/react/hooks/useUser'; import { notifyError, notifySuccess } from '@/portainer/services/notifications'; import { pluralize } from '@/portainer/helpers/strings'; import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; -import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils'; import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription'; import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery'; @@ -52,12 +53,17 @@ export function ServicesDatatable() { const filteredServices = services?.filter( (service) => (canAccessSystemResources && tableState.showSystemResources) || - !isSystemNamespace(service.Namespace) + !namespaces?.[service.Namespace].IsSystem + ); + + const servicesWithIsSystem = useServicesRowData( + filteredServices || [], + namespaces ); return ( row.UID} - isRowSelectable={(row) => !isSystemNamespace(row.original.Namespace)} + isRowSelectable={(row) => !namespaces?.[row.original.Namespace].IsSystem} disableSelect={readOnly} renderTableActions={(selectedRows) => ( @@ -87,6 +93,21 @@ export function ServicesDatatable() { ); } +// useServicesRowData appends the `isSyetem` property to the service data +function useServicesRowData( + services: Service[], + namespaces?: Namespaces +): Service[] { + return useMemo( + () => + services.map((service) => ({ + ...service, + IsSystem: namespaces ? namespaces?.[service.Namespace].IsSystem : false, + })), + [services, namespaces] + ); +} + // needed to apply custom styling to the row cells and not globally. // required in the AC's for this ticket. function servicesRenderRow(row: Row, highlightedItemId?: string) { diff --git a/app/react/kubernetes/services/ServicesView/ServicesDatatable/columns/name.tsx b/app/react/kubernetes/services/ServicesView/ServicesDatatable/columns/name.tsx index 6f80a6a74..88c6bdece 100644 --- a/app/react/kubernetes/services/ServicesView/ServicesDatatable/columns/name.tsx +++ b/app/react/kubernetes/services/ServicesView/ServicesDatatable/columns/name.tsx @@ -1,5 +1,4 @@ import { Authorized } from '@/react/hooks/useUser'; -import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils'; import { Badge } from '@@/Badge'; @@ -11,13 +10,12 @@ export const name = columnHelper.accessor( const isExternal = !row.Labels || !row.Labels['io.portainer.kubernetes.application.owner']; - const isSystem = isSystemNamespace(row.Namespace); - if (isExternal && !isSystem) { + if (isExternal && !row.IsSystem) { name = `${name} external`; } - if (isSystem) { + if (row.IsSystem) { name = `${name} system`; } return name; @@ -27,7 +25,6 @@ export const name = columnHelper.accessor( id: 'name', cell: ({ row }) => { const name = row.original.Name; - const isSystem = isSystemNamespace(row.original.Namespace); const isExternal = !row.original.Labels || @@ -38,13 +35,13 @@ export const name = columnHelper.accessor( {name} - {isSystem && ( + {row.original.IsSystem && ( System )} - {isExternal && !isSystem && ( + {isExternal && !row.original.IsSystem && ( External )} diff --git a/app/react/kubernetes/services/types.ts b/app/react/kubernetes/services/types.ts index 53ec3e514..4b4ceb4ae 100644 --- a/app/react/kubernetes/services/types.ts +++ b/app/react/kubernetes/services/types.ts @@ -38,6 +38,8 @@ export type Service = { ExternalIPs?: Array; CreationTimestamp: string; Applications?: Application[]; + + IsSystem?: boolean; }; export type NodeMetrics = {