diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go
index c8a6ac3609a..6781a76e6d6 100644
--- a/pkg/services/serviceaccounts/api/api.go
+++ b/pkg/services/serviceaccounts/api/api.go
@@ -218,7 +218,13 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *models.ReqC
if page < 1 {
page = 1
}
- serviceAccountSearch, err := api.store.SearchOrgServiceAccounts(ctx, c.OrgId, c.Query("query"), page, perPage, c.SignedInUser)
+ // its okay that it fails, it is only filtering that might be weird, but to safe quard against any weird incoming query param
+ onlyWithExpiredTokens := c.QueryBool("expiredTokens")
+ filter := serviceaccounts.FilterIncludeAll
+ if onlyWithExpiredTokens {
+ filter = serviceaccounts.FilterOnlyExpiredTokens
+ }
+ serviceAccountSearch, err := api.store.SearchOrgServiceAccounts(ctx, c.OrgId, c.Query("query"), filter, page, perPage, c.SignedInUser)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get service accounts for current organization", err)
}
diff --git a/pkg/services/serviceaccounts/database/database.go b/pkg/services/serviceaccounts/database/database.go
index 81623aad044..21c5bc60200 100644
--- a/pkg/services/serviceaccounts/database/database.go
+++ b/pkg/services/serviceaccounts/database/database.go
@@ -288,7 +288,7 @@ func (s *ServiceAccountsStoreImpl) UpdateServiceAccount(ctx context.Context,
}
func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
- ctx context.Context, orgID int64, query string, page int, limit int,
+ ctx context.Context, orgID int64, query string, filter serviceaccounts.ServiceAccountFilter, page int, limit int,
signedInUser *models.SignedInUser,
) (*serviceaccounts.SearchServiceAccountsResult, error) {
searchResult := &serviceaccounts.SearchServiceAccountsResult{
@@ -328,6 +328,20 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
}
+ switch filter {
+ case serviceaccounts.FilterIncludeAll:
+ // pass
+ case serviceaccounts.FilterOnlyExpiredTokens:
+ now := time.Now().Unix()
+ // we do a subquery to remove duplicates coming from joining in api_keys, if we find more than one api key that has expired
+ whereConditions = append(
+ whereConditions,
+ "(SELECT count(*) FROM api_key WHERE api_key.service_account_id = org_user.user_id AND api_key.expires < ?) > 0")
+ whereParams = append(whereParams, now)
+ default:
+ s.log.Warn("invalid filter user for service account filtering", "service account search filtering", filter)
+ }
+
if len(whereConditions) > 0 {
sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
}
diff --git a/pkg/services/serviceaccounts/models.go b/pkg/services/serviceaccounts/models.go
index 5e5a4cf6de1..f350f259a90 100644
--- a/pkg/services/serviceaccounts/models.go
+++ b/pkg/services/serviceaccounts/models.go
@@ -60,3 +60,10 @@ type ServiceAccountProfileDTO struct {
Teams []string `json:"teams" xorm:"-"`
AccessControl map[string]bool `json:"accessControl,omitempty" xorm:"-"`
}
+
+type ServiceAccountFilter string // used for filtering
+
+const (
+ FilterOnlyExpiredTokens ServiceAccountFilter = "expiredTokens"
+ FilterIncludeAll ServiceAccountFilter = "all"
+)
diff --git a/pkg/services/serviceaccounts/serviceaccounts.go b/pkg/services/serviceaccounts/serviceaccounts.go
index 5032dbe87d6..e7b186e8397 100644
--- a/pkg/services/serviceaccounts/serviceaccounts.go
+++ b/pkg/services/serviceaccounts/serviceaccounts.go
@@ -14,7 +14,7 @@ type Service interface {
type Store interface {
CreateServiceAccount(ctx context.Context, orgID int64, name string) (*ServiceAccountDTO, error)
- SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, page int, limit int,
+ SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, filter ServiceAccountFilter, page int, limit int,
signedInUser *models.SignedInUser) (*SearchServiceAccountsResult, error)
UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error)
diff --git a/pkg/services/serviceaccounts/tests/common.go b/pkg/services/serviceaccounts/tests/common.go
index 1de775e5c79..6974ed1de14 100644
--- a/pkg/services/serviceaccounts/tests/common.go
+++ b/pkg/services/serviceaccounts/tests/common.go
@@ -123,7 +123,13 @@ func (s *ServiceAccountsStoreMock) UpdateServiceAccount(ctx context.Context,
return nil, nil
}
-func (s *ServiceAccountsStoreMock) SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, page int, limit int,
+func (s *ServiceAccountsStoreMock) SearchOrgServiceAccounts(
+ ctx context.Context,
+ orgID int64,
+ query string,
+ filter serviceaccounts.ServiceAccountFilter,
+ page int,
+ limit int,
user *models.SignedInUser) (*serviceaccounts.SearchServiceAccountsResult, error) {
s.Calls.SearchOrgServiceAccounts = append(s.Calls.SearchOrgServiceAccounts, []interface{}{ctx, orgID, query, page, limit, user})
return nil, nil
diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx
index 58c7b2b8982..14dbcca88e9 100644
--- a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx
+++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx
@@ -21,6 +21,7 @@ import { contextSrv } from 'app/core/core';
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
import { OrgRolePicker } from '../admin/OrgRolePicker';
import pluralize from 'pluralize';
+import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
interface OwnProps {}
@@ -91,20 +92,34 @@ const ServiceAccountsListPage = ({
{ label: 'All service accounts', value: false },
{ label: 'Expired tokens', value: true },
]}
- onChange={(value) => changeFilter({ name: 'Expired', value })}
- value={filters.find((f) => f.name === 'Expired')?.value}
+ onChange={(value) => changeFilter({ name: 'expiredTokens', value })}
+ value={filters.find((f) => f.name === 'expiredTokens')?.value}
className={styles.filter}
/>
- {contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && (
-