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) => (