feat(admin): web admin panel — user CRUD, role assignment, system health (#150)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #150.
This commit is contained in:
73
apps/gateway/src/admin/admin-health.controller.ts
Normal file
73
apps/gateway/src/admin/admin-health.controller.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
|
||||
import { sql, type Db } from '@mosaic/db';
|
||||
import { createQueue } from '@mosaic/queue';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { AgentService } from '../agent/agent.service.js';
|
||||
import { ProviderService } from '../agent/provider.service.js';
|
||||
import { AdminGuard } from './admin.guard.js';
|
||||
import type { HealthStatusDto, ServiceStatusDto } from './admin.dto.js';
|
||||
|
||||
@Controller('api/admin/health')
|
||||
@UseGuards(AdminGuard)
|
||||
export class AdminHealthController {
|
||||
constructor(
|
||||
@Inject(DB) private readonly db: Db,
|
||||
@Inject(AgentService) private readonly agentService: AgentService,
|
||||
@Inject(ProviderService) private readonly providerService: ProviderService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
async check(): Promise<HealthStatusDto> {
|
||||
const [database, cache] = await Promise.all([this.checkDatabase(), this.checkCache()]);
|
||||
|
||||
const sessions = this.agentService.listSessions();
|
||||
const providers = this.providerService.listProviders();
|
||||
|
||||
const allOk = database.status === 'ok' && cache.status === 'ok';
|
||||
|
||||
return {
|
||||
status: allOk ? 'ok' : 'degraded',
|
||||
database,
|
||||
cache,
|
||||
agentPool: { activeSessions: sessions.length },
|
||||
providers: providers.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
available: p.available,
|
||||
modelCount: p.models.length,
|
||||
})),
|
||||
checkedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
private async checkDatabase(): Promise<ServiceStatusDto> {
|
||||
const start = Date.now();
|
||||
try {
|
||||
await this.db.execute(sql`SELECT 1`);
|
||||
return { status: 'ok', latencyMs: Date.now() - start };
|
||||
} catch (err) {
|
||||
return {
|
||||
status: 'error',
|
||||
latencyMs: Date.now() - start,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async checkCache(): Promise<ServiceStatusDto> {
|
||||
const start = Date.now();
|
||||
const handle = createQueue();
|
||||
try {
|
||||
await handle.redis.ping();
|
||||
return { status: 'ok', latencyMs: Date.now() - start };
|
||||
} catch (err) {
|
||||
return {
|
||||
status: 'error',
|
||||
latencyMs: Date.now() - start,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
} finally {
|
||||
await handle.close().catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user