mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-17 01:17:29 +02:00
fix: add metrics for service account and api tokens (#5478)
This commit is contained in:
parent
961655d5dd
commit
07fcdbb053
@ -96,12 +96,26 @@ export class ApiTokenStore implements IApiTokenStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
count(): Promise<number> {
|
async count(): Promise<number> {
|
||||||
return this.db(TABLE)
|
return this.db(TABLE)
|
||||||
.count('*')
|
.count('*')
|
||||||
.then((res) => Number(res[0].count));
|
.then((res) => Number(res[0].count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async countByType(): Promise<Map<string, number>> {
|
||||||
|
return this.db(TABLE)
|
||||||
|
.select('type')
|
||||||
|
.count('*')
|
||||||
|
.groupBy('type')
|
||||||
|
.then((res) => {
|
||||||
|
const map = new Map<string, number>();
|
||||||
|
res.forEach((row) => {
|
||||||
|
map.set(row.type.toString(), Number(row.count));
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getAll(): Promise<IApiToken[]> {
|
async getAll(): Promise<IApiToken[]> {
|
||||||
const stopTimer = this.timer('getAll');
|
const stopTimer = this.timer('getAll');
|
||||||
const rows = await this.makeTokenProjectQuery();
|
const rows = await this.makeTokenProjectQuery();
|
||||||
|
@ -201,6 +201,16 @@ class UserStore implements IUserStore {
|
|||||||
.then((res) => Number(res[0].count));
|
.then((res) => Number(res[0].count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async countServiceAccounts(): Promise<number> {
|
||||||
|
return this.db(TABLE)
|
||||||
|
.where({
|
||||||
|
deleted_at: null,
|
||||||
|
is_service: true,
|
||||||
|
})
|
||||||
|
.count('*')
|
||||||
|
.then((res) => Number(res[0].count));
|
||||||
|
}
|
||||||
|
|
||||||
destroy(): void {}
|
destroy(): void {}
|
||||||
|
|
||||||
async exists(id: number): Promise<boolean> {
|
async exists(id: number): Promise<boolean> {
|
||||||
|
@ -17,7 +17,11 @@ import { ISegmentStore } from '../../types/stores/segment-store';
|
|||||||
import { IRoleStore } from '../../types/stores/role-store';
|
import { IRoleStore } from '../../types/stores/role-store';
|
||||||
import VersionService from '../../services/version-service';
|
import VersionService from '../../services/version-service';
|
||||||
import { ISettingStore } from '../../types/stores/settings-store';
|
import { ISettingStore } from '../../types/stores/settings-store';
|
||||||
import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../../types';
|
import {
|
||||||
|
FEATURES_EXPORTED,
|
||||||
|
FEATURES_IMPORTED,
|
||||||
|
IApiTokenStore,
|
||||||
|
} from '../../types';
|
||||||
import { CUSTOM_ROOT_ROLE_TYPE } from '../../util';
|
import { CUSTOM_ROOT_ROLE_TYPE } from '../../util';
|
||||||
import { type GetActiveUsers } from './getActiveUsers';
|
import { type GetActiveUsers } from './getActiveUsers';
|
||||||
import { ProjectModeCount } from '../../db/project-store';
|
import { ProjectModeCount } from '../../db/project-store';
|
||||||
@ -31,6 +35,8 @@ export interface InstanceStats {
|
|||||||
versionOSS: string;
|
versionOSS: string;
|
||||||
versionEnterprise?: string;
|
versionEnterprise?: string;
|
||||||
users: number;
|
users: number;
|
||||||
|
serviceAccounts: number;
|
||||||
|
apiTokens: Map<string, number>;
|
||||||
featureToggles: number;
|
featureToggles: number;
|
||||||
projects: ProjectModeCount[];
|
projects: ProjectModeCount[];
|
||||||
contextFields: number;
|
contextFields: number;
|
||||||
@ -78,6 +84,8 @@ export class InstanceStatsService {
|
|||||||
|
|
||||||
private eventStore: IEventStore;
|
private eventStore: IEventStore;
|
||||||
|
|
||||||
|
private apiTokenStore: IApiTokenStore;
|
||||||
|
|
||||||
private versionService: VersionService;
|
private versionService: VersionService;
|
||||||
|
|
||||||
private settingStore: ISettingStore;
|
private settingStore: ISettingStore;
|
||||||
@ -106,6 +114,7 @@ export class InstanceStatsService {
|
|||||||
settingStore,
|
settingStore,
|
||||||
clientInstanceStore,
|
clientInstanceStore,
|
||||||
eventStore,
|
eventStore,
|
||||||
|
apiTokenStore,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
IUnleashStores,
|
IUnleashStores,
|
||||||
| 'featureToggleStore'
|
| 'featureToggleStore'
|
||||||
@ -120,6 +129,7 @@ export class InstanceStatsService {
|
|||||||
| 'settingStore'
|
| 'settingStore'
|
||||||
| 'clientInstanceStore'
|
| 'clientInstanceStore'
|
||||||
| 'eventStore'
|
| 'eventStore'
|
||||||
|
| 'apiTokenStore'
|
||||||
>,
|
>,
|
||||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
||||||
versionService: VersionService,
|
versionService: VersionService,
|
||||||
@ -142,6 +152,7 @@ export class InstanceStatsService {
|
|||||||
this.logger = getLogger('services/stats-service.js');
|
this.logger = getLogger('services/stats-service.js');
|
||||||
this.getActiveUsers = getActiveUsers;
|
this.getActiveUsers = getActiveUsers;
|
||||||
this.getProductionChanges = getProductionChanges;
|
this.getProductionChanges = getProductionChanges;
|
||||||
|
this.apiTokenStore = apiTokenStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshStatsSnapshot(): Promise<void> {
|
async refreshStatsSnapshot(): Promise<void> {
|
||||||
@ -194,6 +205,8 @@ export class InstanceStatsService {
|
|||||||
const [
|
const [
|
||||||
featureToggles,
|
featureToggles,
|
||||||
users,
|
users,
|
||||||
|
serviceAccounts,
|
||||||
|
apiTokens,
|
||||||
activeUsers,
|
activeUsers,
|
||||||
projects,
|
projects,
|
||||||
contextFields,
|
contextFields,
|
||||||
@ -213,6 +226,8 @@ export class InstanceStatsService {
|
|||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
this.getToggleCount(),
|
this.getToggleCount(),
|
||||||
this.userStore.count(),
|
this.userStore.count(),
|
||||||
|
this.userStore.countServiceAccounts(),
|
||||||
|
this.apiTokenStore.countByType(),
|
||||||
this.getActiveUsers(),
|
this.getActiveUsers(),
|
||||||
this.getProjectModeCount(),
|
this.getProjectModeCount(),
|
||||||
this.contextFieldStore.count(),
|
this.contextFieldStore.count(),
|
||||||
@ -237,6 +252,8 @@ export class InstanceStatsService {
|
|||||||
versionOSS: versionInfo.current.oss,
|
versionOSS: versionInfo.current.oss,
|
||||||
versionEnterprise: versionInfo.current.enterprise,
|
versionEnterprise: versionInfo.current.enterprise,
|
||||||
users,
|
users,
|
||||||
|
serviceAccounts,
|
||||||
|
apiTokens,
|
||||||
activeUsers,
|
activeUsers,
|
||||||
featureToggles,
|
featureToggles,
|
||||||
projects,
|
projects,
|
||||||
|
@ -31,8 +31,8 @@ export default class MetricsMonitor {
|
|||||||
poolMetricsTimer?: Timer;
|
poolMetricsTimer?: Timer;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.timer = null;
|
this.timer = undefined;
|
||||||
this.poolMetricsTimer = null;
|
this.poolMetricsTimer = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
startMonitoring(
|
startMonitoring(
|
||||||
@ -44,7 +44,7 @@ export default class MetricsMonitor {
|
|||||||
db: Knex,
|
db: Knex,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!config.server.serverMetrics) {
|
if (!config.server.serverMetrics) {
|
||||||
return;
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { eventStore } = stores;
|
const { eventStore } = stores;
|
||||||
@ -94,6 +94,15 @@ export default class MetricsMonitor {
|
|||||||
name: 'users_total',
|
name: 'users_total',
|
||||||
help: 'Number of users',
|
help: 'Number of users',
|
||||||
});
|
});
|
||||||
|
const serviceAccounts = new client.Gauge({
|
||||||
|
name: 'service_accounts_total',
|
||||||
|
help: 'Number of service accounts',
|
||||||
|
});
|
||||||
|
const apiTokens = new client.Gauge({
|
||||||
|
name: 'api_tokens_total',
|
||||||
|
help: 'Number of API tokens',
|
||||||
|
labelNames: ['type'],
|
||||||
|
});
|
||||||
const usersActive7days = new client.Gauge({
|
const usersActive7days = new client.Gauge({
|
||||||
name: 'users_active_7',
|
name: 'users_active_7',
|
||||||
help: 'Number of users active in the last 7 days',
|
help: 'Number of users active in the last 7 days',
|
||||||
@ -214,6 +223,15 @@ export default class MetricsMonitor {
|
|||||||
usersTotal.reset();
|
usersTotal.reset();
|
||||||
usersTotal.set(stats.users);
|
usersTotal.set(stats.users);
|
||||||
|
|
||||||
|
serviceAccounts.reset();
|
||||||
|
serviceAccounts.set(stats.serviceAccounts);
|
||||||
|
|
||||||
|
apiTokens.reset();
|
||||||
|
|
||||||
|
for (const [type, value] of stats.apiTokens) {
|
||||||
|
apiTokens.labels(type).set(value);
|
||||||
|
}
|
||||||
|
|
||||||
usersActive7days.reset();
|
usersActive7days.reset();
|
||||||
usersActive7days.set(stats.activeUsers.last7);
|
usersActive7days.set(stats.activeUsers.last7);
|
||||||
usersActive30days.reset();
|
usersActive30days.reset();
|
||||||
@ -420,6 +438,8 @@ export default class MetricsMonitor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.configureDbMetrics(db, eventBus);
|
this.configureDbMetrics(db, eventBus);
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
stopMonitoring(): void {
|
stopMonitoring(): void {
|
||||||
|
@ -105,6 +105,8 @@ class InstanceAdminController extends Controller {
|
|||||||
sum: 'some-sha256-hash',
|
sum: 'some-sha256-hash',
|
||||||
timestamp: new Date(2023, 6, 12, 10, 0, 0, 0),
|
timestamp: new Date(2023, 6, 12, 10, 0, 0, 0),
|
||||||
users: 10,
|
users: 10,
|
||||||
|
serviceAccounts: 2,
|
||||||
|
apiTokens: new Map([]),
|
||||||
versionEnterprise: '5.1.7',
|
versionEnterprise: '5.1.7',
|
||||||
versionOSS: '5.1.7',
|
versionOSS: '5.1.7',
|
||||||
activeUsers: {
|
activeUsers: {
|
||||||
|
@ -7,4 +7,5 @@ export interface IApiTokenStore extends Store<IApiToken, string> {
|
|||||||
setExpiry(secret: string, expiresAt: Date): Promise<IApiToken>;
|
setExpiry(secret: string, expiresAt: Date): Promise<IApiToken>;
|
||||||
markSeenAt(secrets: string[]): Promise<void>;
|
markSeenAt(secrets: string[]): Promise<void>;
|
||||||
count(): Promise<number>;
|
count(): Promise<number>;
|
||||||
|
countByType(): Promise<Map<string, number>>;
|
||||||
}
|
}
|
||||||
|
@ -32,4 +32,5 @@ export interface IUserStore extends Store<IUser, number> {
|
|||||||
incLoginAttempts(user: IUser): Promise<void>;
|
incLoginAttempts(user: IUser): Promise<void>;
|
||||||
successfullyLogin(user: IUser): Promise<void>;
|
successfullyLogin(user: IUser): Promise<void>;
|
||||||
count(): Promise<number>;
|
count(): Promise<number>;
|
||||||
|
countServiceAccounts(): Promise<number>;
|
||||||
}
|
}
|
||||||
|
9
src/test/fixtures/fake-api-token-store.ts
vendored
9
src/test/fixtures/fake-api-token-store.ts
vendored
@ -1,5 +1,9 @@
|
|||||||
import { IApiTokenStore } from '../../lib/types/stores/api-token-store';
|
import { IApiTokenStore } from '../../lib/types/stores/api-token-store';
|
||||||
import { IApiToken, IApiTokenCreate } from '../../lib/types/models/api-token';
|
import {
|
||||||
|
ApiTokenType,
|
||||||
|
IApiToken,
|
||||||
|
IApiTokenCreate,
|
||||||
|
} from '../../lib/types/models/api-token';
|
||||||
|
|
||||||
import NotFoundError from '../../lib/error/notfound-error';
|
import NotFoundError from '../../lib/error/notfound-error';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
@ -8,6 +12,9 @@ export default class FakeApiTokenStore
|
|||||||
extends EventEmitter
|
extends EventEmitter
|
||||||
implements IApiTokenStore
|
implements IApiTokenStore
|
||||||
{
|
{
|
||||||
|
countByType(): Promise<Map<ApiTokenType, number>> {
|
||||||
|
return Promise.resolve(new Map());
|
||||||
|
}
|
||||||
tokens: IApiToken[] = [];
|
tokens: IApiToken[] = [];
|
||||||
|
|
||||||
async delete(key: string): Promise<void> {
|
async delete(key: string): Promise<void> {
|
||||||
|
3
src/test/fixtures/fake-user-store.ts
vendored
3
src/test/fixtures/fake-user-store.ts
vendored
@ -14,6 +14,9 @@ class UserStoreMock implements IUserStore {
|
|||||||
this.idSeq = 1;
|
this.idSeq = 1;
|
||||||
this.data = [];
|
this.data = [];
|
||||||
}
|
}
|
||||||
|
countServiceAccounts(): Promise<number> {
|
||||||
|
return Promise.resolve(0);
|
||||||
|
}
|
||||||
|
|
||||||
async hasUser({
|
async hasUser({
|
||||||
id,
|
id,
|
||||||
|
Loading…
Reference in New Issue
Block a user