diff --git a/api/http/models/kubernetes/service_accounts.go b/api/http/models/kubernetes/service_accounts.go index 13b279222..4540ffb55 100644 --- a/api/http/models/kubernetes/service_accounts.go +++ b/api/http/models/kubernetes/service_accounts.go @@ -4,13 +4,17 @@ import ( "errors" "net/http" "time" + + "k8s.io/apimachinery/pkg/types" ) type ( K8sServiceAccount struct { Name string `json:"name"` + UID types.UID `json:"uid"` Namespace string `json:"namespace"` CreationDate time.Time `json:"creationDate"` + IsSystem bool `json:"isSystem"` } // K8sServiceAcountDeleteRequests is a mapping of namespace names to a slice of service account names. diff --git a/api/kubernetes/cli/service_account.go b/api/kubernetes/cli/service_account.go index d115f89d4..831080efc 100644 --- a/api/kubernetes/cli/service_account.go +++ b/api/kubernetes/cli/service_account.go @@ -53,18 +53,20 @@ func (kcl *KubeClient) fetchServiceAccounts(namespace string) ([]models.K8sServi results := make([]models.K8sServiceAccount, 0) for _, serviceAccount := range serviceAccounts.Items { - results = append(results, parseServiceAccount(serviceAccount)) + results = append(results, kcl.parseServiceAccount(serviceAccount)) } return results, nil } // parseServiceAccount converts a corev1.ServiceAccount object to a models.K8sServiceAccount object. -func parseServiceAccount(serviceAccount corev1.ServiceAccount) models.K8sServiceAccount { +func (kcl *KubeClient) parseServiceAccount(serviceAccount corev1.ServiceAccount) models.K8sServiceAccount { return models.K8sServiceAccount{ Name: serviceAccount.Name, + UID: serviceAccount.UID, Namespace: serviceAccount.Namespace, CreationDate: serviceAccount.CreationTimestamp.Time, + IsSystem: kcl.isSystemServiceAccount(serviceAccount.Namespace), } } @@ -84,6 +86,10 @@ func (kcl *KubeClient) GetPortainerUserServiceAccount(tokenData *portainer.Token return serviceAccount, nil } +func (kcl *KubeClient) isSystemServiceAccount(namespace string) bool { + return kcl.isSystemNamespace(namespace) +} + // DeleteServices processes a K8sServiceDeleteRequest by deleting each service // in its given namespace. func (kcl *KubeClient) DeleteServiceAccounts(reqs kubernetes.K8sServiceAccountDeleteRequests) error { @@ -101,7 +107,7 @@ func (kcl *KubeClient) DeleteServiceAccounts(reqs kubernetes.K8sServiceAccountDe return err } - if kcl.isSystemNamespace(sa.Namespace) { + if kcl.isSystemServiceAccount(sa.Namespace) { return fmt.Errorf("cannot delete system service account %q", namespace+"/"+serviceName) } diff --git a/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/ServiceAccountsDatatable.tsx b/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/ServiceAccountsDatatable.tsx index eef8b94bf..09d691be6 100644 --- a/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/ServiceAccountsDatatable.tsx +++ b/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/ServiceAccountsDatatable.tsx @@ -6,13 +6,11 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { Authorized } from '@/react/hooks/useUser'; import { notifyError, notifySuccess } from '@/portainer/services/notifications'; import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription'; -import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery'; import { DefaultDatatableSettings, TableSettings as KubeTableSettings, } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton'; -import { isSystemNamespace } from '@/react/kubernetes/namespaces/queries/useIsSystemNamespace'; import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; import { Datatable, TableSettingsMenu } from '@@/datatables'; @@ -24,7 +22,7 @@ import { import { ServiceAccount } from '../types'; -import { getColumns } from './columns'; +import { columns } from './columns'; import { useDeleteServiceAccountsMutation } from './queries/useDeleteServiceAccountsMutation'; import { useGetAllServiceAccountsQuery } from './queries/useGetAllServiceAccountsQuery'; @@ -42,22 +40,15 @@ export function ServiceAccountsDatatable() { ...filteredColumnsSettings(set), }) ); - const namespacesQuery = useNamespacesQuery(environmentId); - const namespaces = namespacesQuery.data; const serviceAccountsQuery = useGetAllServiceAccountsQuery(environmentId, { refetchInterval: tableState.autoRefreshRate * 1000, - enabled: namespacesQuery.isSuccess, }); - - const columns = getColumns(namespaces); const filteredServiceAccounts = useMemo( () => tableState.showSystemResources ? serviceAccountsQuery.data - : serviceAccountsQuery.data?.filter( - (sa) => !isSystemNamespace(sa.namespace, namespaces) - ), - [namespaces, serviceAccountsQuery.data, tableState.showSystemResources] + : serviceAccountsQuery.data?.filter((sa) => !sa.isSystem), + [serviceAccountsQuery.data, tableState.showSystemResources] ); return ( @@ -69,10 +60,8 @@ export function ServiceAccountsDatatable() { emptyContentLabel="No service accounts found" title="Service Accounts" titleIcon={User} - getRowId={(row) => `${row.namespace}-${row.name}`} - isRowSelectable={(row) => - !isSystemNamespace(row.original.namespace, namespaces) - } + getRowId={(row) => row.uid} + isRowSelectable={(row) => !row.original.isSystem} renderTableActions={(selectedRows) => ( )} diff --git a/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/columns/index.tsx b/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/columns/index.tsx index 2ac51596b..006a506ec 100644 --- a/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/columns/index.tsx +++ b/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/columns/index.tsx @@ -1,9 +1,5 @@ -import { PortainerNamespace } from '@/react/kubernetes/namespaces/types'; - import { name } from './name'; import { namespace } from './namespace'; import { created } from './created'; -export function getColumns(namespaces?: PortainerNamespace[]) { - return [name(namespaces), namespace, created]; -} +export const columns = [name, namespace, created]; diff --git a/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/columns/name.tsx b/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/columns/name.tsx index 9b18567b2..e4749af9b 100644 --- a/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/columns/name.tsx +++ b/app/react/kubernetes/more-resources/ServiceAccountsView/ServiceAccountsDatatable/columns/name.tsx @@ -1,30 +1,23 @@ -import { isSystemNamespace } from '@/react/kubernetes/namespaces/queries/useIsSystemNamespace'; -import { PortainerNamespace } from '@/react/kubernetes/namespaces/types'; - import { SystemBadge } from '@@/Badge/SystemBadge'; import { columnHelper } from './helper'; -export function name(namespaces?: PortainerNamespace[]) { - return columnHelper.accessor( - (row) => { - let result = row.name; - if (isSystemNamespace(row.namespace, namespaces)) { - result += ' system'; - } - return result; - }, - { - header: 'Name', - id: 'name', - cell: ({ row }) => ( -
-
{row.original.name}
- {isSystemNamespace(row.original.namespace, namespaces) && ( - - )} -
- ), +export const name = columnHelper.accessor( + (row) => { + let result = row.name; + if (row.isSystem) { + result += ' system'; } - ); -} + return result; + }, + { + header: 'Name', + id: 'name', + cell: ({ row }) => ( +
+
{row.original.name}
+ {row.original.isSystem && } +
+ ), + } +); diff --git a/app/react/kubernetes/more-resources/ServiceAccountsView/types.ts b/app/react/kubernetes/more-resources/ServiceAccountsView/types.ts index a2134c96a..092bff5c5 100644 --- a/app/react/kubernetes/more-resources/ServiceAccountsView/types.ts +++ b/app/react/kubernetes/more-resources/ServiceAccountsView/types.ts @@ -1,5 +1,7 @@ export type ServiceAccount = { name: string; + uid: string; namespace: string; creationDate: string; + isSystem: boolean; };