@capybara-hq/kernel 0.1.0-rc.19 → 0.1.0-rc.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bootstrap.json +25 -25
- package/dist/admin/admin-agents.controller.d.ts +28 -0
- package/dist/admin/admin-agents.controller.js +79 -0
- package/dist/admin/admin-connectors.controller.d.ts +29 -0
- package/dist/admin/admin-connectors.controller.js +79 -0
- package/dist/admin/admin-items.schemas.d.ts +42 -0
- package/dist/admin/admin-items.schemas.js +48 -0
- package/dist/admin/admin.controller.d.ts +11 -20
- package/dist/admin/admin.controller.js +28 -34
- package/dist/admin/admin.module.d.ts +4 -1
- package/dist/admin/admin.module.js +50 -4
- package/dist/admin/admin.schemas.d.ts +4 -0
- package/dist/admin/admin.schemas.js +4 -1
- package/dist/admin/audit.controller.d.ts +14 -0
- package/dist/admin/audit.controller.js +42 -0
- package/dist/admin/audit.schemas.d.ts +8 -0
- package/dist/admin/audit.schemas.js +17 -0
- package/dist/admin/health.service.d.ts +104 -0
- package/dist/admin/health.service.js +246 -0
- package/dist/admin/overview.controller.d.ts +31 -0
- package/dist/admin/overview.controller.js +78 -0
- package/dist/admin/overview.schemas.d.ts +8 -0
- package/dist/admin/overview.schemas.js +13 -0
- package/dist/admin/overview.service.d.ts +112 -0
- package/dist/admin/overview.service.js +223 -0
- package/dist/admin/platform-audit.service.d.ts +41 -0
- package/dist/admin/platform-audit.service.js +70 -0
- package/dist/admin/usage.service.d.ts +149 -0
- package/dist/admin/usage.service.js +321 -0
- package/dist/agents/admin-agents.d.ts +92 -0
- package/dist/agents/admin-agents.js +170 -0
- package/dist/agents/agents.module.d.ts +4 -3
- package/dist/agents/agents.module.js +7 -3
- package/dist/agents/agents.service.d.ts +41 -3
- package/dist/agents/agents.service.js +113 -27
- package/dist/agents/definition.d.ts +16 -0
- package/dist/agents/definition.js +32 -2
- package/dist/agents/org-templates.js +4 -0
- package/dist/background/launch.d.ts +1 -1
- package/dist/background/launch.js +3 -3
- package/dist/background/tasks.service.d.ts +35 -3
- package/dist/background/tasks.service.js +91 -36
- package/dist/catalog/catalog.module.js +8 -2
- package/dist/channels/channels.module.js +11 -2
- package/dist/channels/supervisor.d.ts +2 -0
- package/dist/channels/supervisor.js +4 -0
- package/dist/conversations/events.js +1 -1
- package/dist/conversations/titles.js +4 -8
- package/dist/db/kernel-schema.d.ts +8 -1
- package/dist/devices/devices.module.js +5 -2
- package/dist/errors.d.ts +22 -2
- package/dist/errors.js +22 -2
- package/dist/evals/evals.module.js +11 -2
- package/dist/extensions/permission-view.js +3 -3
- package/dist/http/api.module.js +10 -1
- package/dist/identity/settings.js +3 -2
- package/dist/identity/write-actor.d.ts +11 -0
- package/dist/identity/write-actor.js +7 -0
- package/dist/inbox/inbox.service.d.ts +2 -1
- package/dist/inbox/platform-inbox.service.d.ts +7 -1
- package/dist/inbox/platform-inbox.service.js +20 -2
- package/dist/infra/presence/presence.module.d.ts +6 -0
- package/dist/infra/presence/presence.module.js +22 -0
- package/dist/infra/presence/replica-presence.d.ts +87 -0
- package/dist/infra/presence/replica-presence.js +189 -0
- package/dist/kernel.module.js +3 -0
- package/dist/lifecycle/kernel-lifecycle.d.ts +3 -1
- package/dist/lifecycle/kernel-lifecycle.js +17 -3
- package/dist/local-devices/local-device-gateway.d.ts +6 -0
- package/dist/local-devices/local-device-gateway.js +12 -0
- package/dist/overrides.d.ts +7 -1
- package/dist/overrides.js +4 -0
- package/dist/platform/exclusions.js +15 -0
- package/dist/platform/operation.d.ts +1 -1
- package/dist/platform/operation.js +1 -1
- package/dist/platform/ops/admin-items.ops.d.ts +37 -0
- package/dist/platform/ops/admin-items.ops.js +278 -0
- package/dist/platform/ops/admin.ops.d.ts +10 -10
- package/dist/platform/ops/admin.ops.js +111 -42
- package/dist/platform/ops/agent-skills.ops.d.ts +29 -3
- package/dist/platform/ops/agent-skills.ops.js +290 -7
- package/dist/platform/ops/inbox.ops.js +7 -4
- package/dist/platform/ops/platform.ops.js +1 -1
- package/dist/platform/ops/skills.ops.js +1 -57
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +8 -0
- package/dist/roles/gateway-role.d.ts +4 -0
- package/dist/roles/gateway-role.js +8 -0
- package/dist/runtime/run-jobs.js +4 -2
- package/dist/sandbox/sandbox-file-reader.d.ts +35 -0
- package/dist/sandbox/sandbox-file-reader.js +98 -0
- package/dist/skills/agent-skills.controller.d.ts +48 -0
- package/dist/skills/agent-skills.controller.js +131 -0
- package/dist/skills/agent-skills.service.d.ts +262 -0
- package/dist/skills/agent-skills.service.js +996 -0
- package/dist/skills/catalog.d.ts +8 -6
- package/dist/skills/catalog.js +8 -4
- package/dist/skills/changes.controller.d.ts +5 -22
- package/dist/skills/changes.controller.js +7 -55
- package/dist/skills/changes.service.d.ts +69 -115
- package/dist/skills/changes.service.js +249 -599
- package/dist/skills/conversation-catalog.d.ts +53 -0
- package/dist/skills/conversation-catalog.js +149 -0
- package/dist/skills/invocation-keys.d.ts +1 -1
- package/dist/skills/invocation-keys.js +1 -1
- package/dist/skills/package.d.ts +0 -5
- package/dist/skills/package.js +0 -14
- package/dist/skills/skills.controller.js +1 -10
- package/dist/skills/skills.module.js +15 -2
- package/dist/skills/skills.schemas.d.ts +11 -15
- package/dist/skills/skills.schemas.js +27 -20
- package/dist/skills/skills.service.d.ts +37 -30
- package/dist/skills/skills.service.js +107 -59
- package/dist/skills/usable.d.ts +4 -3
- package/dist/skills/usable.js +13 -4
- package/dist/tools/admin-connectors.d.ts +69 -0
- package/dist/tools/admin-connectors.js +135 -0
- package/dist/tools/builtin-names.d.ts +9 -14
- package/dist/tools/builtin-names.js +13 -22
- package/dist/tools/builtins/builtin-tools.d.ts +2 -3
- package/dist/tools/builtins/builtin-tools.js +21 -26
- package/dist/tools/builtins/sandbox/bash.js +2 -2
- package/dist/tools/builtins/skill-load.js +1 -0
- package/dist/tools/connectors.controller.d.ts +5 -1
- package/dist/tools/connectors.controller.js +17 -3
- package/dist/tools/connectors.service.d.ts +20 -1
- package/dist/tools/connectors.service.js +61 -0
- package/dist/tools/legacy-keys.js +0 -1
- package/dist/tools/org-tool-switches.d.ts +3 -2
- package/dist/tools/org-tool-switches.js +4 -4
- package/dist/tools/policy.d.ts +7 -9
- package/dist/tools/policy.js +11 -22
- package/dist/tools/tools.module.js +3 -0
- package/package.json +1 -1
- package/src/migrations/0109_audit_platform_idx.sql +5 -0
- package/src/migrations/0110_usage_time_idx.sql +8 -0
- package/src/migrations/0111_agent_skill_sharing.sql +22 -0
- package/src/migrations/0112_skill_share_requests.sql +4 -0
- package/dist/tools/builtins/skill-write.d.ts +0 -59
- package/dist/tools/builtins/skill-write.js +0 -244
|
@@ -5,18 +5,64 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { Module } from "@nestjs/common";
|
|
8
|
+
import { CatalogModule } from "../catalog/catalog.module.js";
|
|
9
|
+
import { ChannelsModule } from "../channels/channels.module.js";
|
|
10
|
+
import { TitlesModule } from "../conversations/titles.js";
|
|
11
|
+
import { EvalsModule } from "../evals/evals.module.js";
|
|
12
|
+
import { ExtensionsModule } from "../extensions/extensions.module.js";
|
|
8
13
|
import { IdentityModule } from "../identity/identity.module.js";
|
|
14
|
+
import { InboxModule } from "../inbox/inbox.module.js";
|
|
15
|
+
import { ModelsModule } from "../models/models.module.js";
|
|
9
16
|
import { OrgsModule } from "../orgs/orgs.module.js";
|
|
17
|
+
import { ProvidersModule } from "../runtime/providers.js";
|
|
18
|
+
import { RunJobsModule } from "../runtime/run-jobs.js";
|
|
19
|
+
import { UsageReportJobsModule } from "../runtime/usage-report-jobs.js";
|
|
20
|
+
import { AdminHealthService } from "./health.service.js";
|
|
21
|
+
import { AdminOverviewService } from "./overview.service.js";
|
|
22
|
+
import { PlatformAuditService } from "./platform-audit.service.js";
|
|
10
23
|
import { SetupService } from "./setup.service.js";
|
|
11
24
|
import { SetupLockModule } from "./setup-lock.js";
|
|
12
|
-
|
|
25
|
+
import { AdminUsageService } from "./usage.service.js";
|
|
26
|
+
/**
|
|
27
|
+
* Deployment administration: the setup wizard, the platform-level audit log and the admin overview
|
|
28
|
+
* with its health read and seven-day usage aggregate (its controllers are the api role's, `ApiModule`).
|
|
29
|
+
*/
|
|
13
30
|
let AdminModule = class AdminModule {
|
|
14
31
|
};
|
|
15
32
|
AdminModule = __decorate([
|
|
16
33
|
Module({
|
|
17
|
-
imports: [
|
|
18
|
-
|
|
19
|
-
|
|
34
|
+
imports: [
|
|
35
|
+
IdentityModule,
|
|
36
|
+
OrgsModule,
|
|
37
|
+
SetupLockModule,
|
|
38
|
+
ExtensionsModule,
|
|
39
|
+
CatalogModule,
|
|
40
|
+
ProvidersModule,
|
|
41
|
+
InboxModule,
|
|
42
|
+
// the kernel queues the health overview counts, as their owners register them (shared
|
|
43
|
+
// instances: no queue connection of its own)
|
|
44
|
+
RunJobsModule,
|
|
45
|
+
ChannelsModule,
|
|
46
|
+
TitlesModule,
|
|
47
|
+
UsageReportJobsModule,
|
|
48
|
+
EvalsModule,
|
|
49
|
+
// the price table of the usage aggregate
|
|
50
|
+
ModelsModule,
|
|
51
|
+
],
|
|
52
|
+
providers: [
|
|
53
|
+
SetupService,
|
|
54
|
+
PlatformAuditService,
|
|
55
|
+
AdminOverviewService,
|
|
56
|
+
AdminHealthService,
|
|
57
|
+
AdminUsageService,
|
|
58
|
+
],
|
|
59
|
+
exports: [
|
|
60
|
+
SetupService,
|
|
61
|
+
PlatformAuditService,
|
|
62
|
+
AdminOverviewService,
|
|
63
|
+
AdminHealthService,
|
|
64
|
+
AdminUsageService,
|
|
65
|
+
],
|
|
20
66
|
})
|
|
21
67
|
], AdminModule);
|
|
22
68
|
export { AdminModule };
|
|
@@ -62,4 +62,8 @@ export declare const DeploymentSettingsBody: z.ZodObject<{
|
|
|
62
62
|
export declare const PurposeParams: z.ZodObject<{
|
|
63
63
|
purpose: z.ZodString;
|
|
64
64
|
}, z.core.$strip>;
|
|
65
|
+
/** `:orgId` of an admin route: any org, the super admin need not be a member. */
|
|
66
|
+
export declare const AdminOrgParams: z.ZodObject<{
|
|
67
|
+
orgId: z.ZodString;
|
|
68
|
+
}, z.core.$strip>;
|
|
65
69
|
export declare const ADMIN_ROUTES: readonly RouteSchemas[];
|
|
@@ -25,8 +25,9 @@ export const UpdateAccountBody = z.object({
|
|
|
25
25
|
/** At least one key; `runConcurrencyOverrides` merges by org, a null value drops that org's. */
|
|
26
26
|
export const DeploymentSettingsBody = DEPLOYMENT_SETTINGS_PATCH.refine((b) => Object.keys(b).length > 0, { message: "at least one setting is required" });
|
|
27
27
|
export const PurposeParams = z.object({ purpose: z.string().min(1).max(100) });
|
|
28
|
+
/** `:orgId` of an admin route: any org, the super admin need not be a member. */
|
|
29
|
+
export const AdminOrgParams = z.object({ orgId: z.string().min(1).max(100) });
|
|
28
30
|
export const ADMIN_ROUTES = [
|
|
29
|
-
{ route: "GET /api/v1/admin/overview" },
|
|
30
31
|
{ route: "GET /api/v1/admin/accounts", query: AccountListQuery },
|
|
31
32
|
{ route: "POST /api/v1/admin/accounts", body: CreateAccountBody },
|
|
32
33
|
{
|
|
@@ -35,6 +36,8 @@ export const ADMIN_ROUTES = [
|
|
|
35
36
|
body: UpdateAccountBody,
|
|
36
37
|
},
|
|
37
38
|
{ route: "GET /api/v1/admin/orgs" },
|
|
39
|
+
// `:orgId` is the conversation's org for the operation, so never an operation field
|
|
40
|
+
{ route: "GET /api/v1/admin/orgs/:orgId/members" },
|
|
38
41
|
{ route: "GET /api/v1/admin/settings" },
|
|
39
42
|
{ route: "PUT /api/v1/admin/settings", body: DeploymentSettingsBody },
|
|
40
43
|
{ route: "GET /api/v1/admin/models" },
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import { AuditListQuery } from "./audit.schemas.js";
|
|
3
|
+
import { type PlatformAuditPage, PlatformAuditService } from "./platform-audit.service.js";
|
|
4
|
+
/**
|
|
5
|
+
* The platform-level audit log (`GET /api/v1/admin/audit`, admin overview spec §5.2, decision D2):
|
|
6
|
+
* events with no org, newest first, keyset paged; never an event's `detail`. Super admins only;
|
|
7
|
+
* the read itself is not audited (D5).
|
|
8
|
+
*/
|
|
9
|
+
export declare class AdminAuditController {
|
|
10
|
+
private readonly audit;
|
|
11
|
+
constructor(audit: PlatformAuditService);
|
|
12
|
+
/** `next` is the `after` of the next page, null at the end. */
|
|
13
|
+
list(q: z.infer<typeof AuditListQuery>): Promise<PlatformAuditPage>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Controller, Get, Inject } from "@nestjs/common";
|
|
11
|
+
import { SuperAdmin } from "../identity/principal.js";
|
|
12
|
+
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
13
|
+
import { ZQuery } from "../infra/validation/zod-params.js";
|
|
14
|
+
import { AuditListQuery } from "./audit.schemas.js";
|
|
15
|
+
import { PlatformAuditService } from "./platform-audit.service.js";
|
|
16
|
+
class AuditListQueryDto extends createZodDto(AuditListQuery, "AuditListQuery") {
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The platform-level audit log (`GET /api/v1/admin/audit`, admin overview spec §5.2, decision D2):
|
|
20
|
+
* events with no org, newest first, keyset paged; never an event's `detail`. Super admins only;
|
|
21
|
+
* the read itself is not audited (D5).
|
|
22
|
+
*/
|
|
23
|
+
let AdminAuditController = class AdminAuditController {
|
|
24
|
+
audit;
|
|
25
|
+
constructor(audit) {
|
|
26
|
+
this.audit = audit;
|
|
27
|
+
}
|
|
28
|
+
/** `next` is the `after` of the next page, null at the end. */
|
|
29
|
+
list(q) {
|
|
30
|
+
return this.audit.list(q);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
__decorate([
|
|
34
|
+
Get(),
|
|
35
|
+
__param(0, ZQuery(AuditListQueryDto))
|
|
36
|
+
], AdminAuditController.prototype, "list", null);
|
|
37
|
+
AdminAuditController = __decorate([
|
|
38
|
+
Controller("api/v1/admin/audit"),
|
|
39
|
+
SuperAdmin(),
|
|
40
|
+
__param(0, Inject(PlatformAuditService))
|
|
41
|
+
], AdminAuditController);
|
|
42
|
+
export { AdminAuditController };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { RouteSchemas } from "../platform/route-schemas.js";
|
|
3
|
+
/** `GET /api/v1/admin/audit`: keyset paging by event id, newest first (admin overview spec §5.2). */
|
|
4
|
+
export declare const AuditListQuery: z.ZodObject<{
|
|
5
|
+
after: z.ZodOptional<z.ZodString>;
|
|
6
|
+
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare const AUDIT_ROUTES: readonly RouteSchemas[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** The largest `bigserial` id; a larger `after` would make PG refuse the comparison. */
|
|
3
|
+
const MAX_BIGINT = 9223372036854775807n;
|
|
4
|
+
/** `GET /api/v1/admin/audit`: keyset paging by event id, newest first (admin overview spec §5.2). */
|
|
5
|
+
export const AuditListQuery = z.object({
|
|
6
|
+
after: z
|
|
7
|
+
.string()
|
|
8
|
+
.regex(/^\d{1,19}$/, "after must be an event id (digits)")
|
|
9
|
+
// checks do not abort: only digits reach BigInt
|
|
10
|
+
.refine((v) => !/^\d{1,19}$/.test(v) || BigInt(v) <= MAX_BIGINT, "after must be an event id")
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("The id of the last event of the previous page (its next); absent: the newest."),
|
|
13
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
14
|
+
});
|
|
15
|
+
export const AUDIT_ROUTES = [
|
|
16
|
+
{ route: "GET /api/v1/admin/audit", query: AuditListQuery },
|
|
17
|
+
];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Queue } from "bullmq";
|
|
2
|
+
import type { Redis } from "ioredis";
|
|
3
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
4
|
+
import { type LiveReplica, ReplicaPresence } from "../infra/presence/replica-presence.js";
|
|
5
|
+
import type { KernelLogger } from "../log.js";
|
|
6
|
+
/** The kernel's own queues, in the order the overview lists them (extension queues are not read). */
|
|
7
|
+
export declare const KERNEL_QUEUE_NAMES: readonly ["cap-kernel-runs", "cap-kernel-subagent-runs", "cap-kernel-channels", "cap-kernel-maintenance", "cap-kernel-plugin-scope", "cap-kernel-titles", "cap-kernel-usage-report", "cap-kernel-evals"];
|
|
8
|
+
/** How long each source's reads may take before the answer marks it unavailable. */
|
|
9
|
+
export declare const HEALTH_READ_TIMEOUT_MS = 3000;
|
|
10
|
+
/** A live replica as the overview shows it. */
|
|
11
|
+
export interface AdminHealthReplica {
|
|
12
|
+
/** The process id (the one extension status rows and the gateway heartbeat use). */
|
|
13
|
+
id: string;
|
|
14
|
+
roles: LiveReplica["roles"];
|
|
15
|
+
version: string;
|
|
16
|
+
startedAt: string;
|
|
17
|
+
httpReplicaId: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** One kernel queue: counts only, never job data or a failure text (spec §9). */
|
|
20
|
+
export interface AdminHealthQueue {
|
|
21
|
+
name: (typeof KERNEL_QUEUE_NAMES)[number];
|
|
22
|
+
/** Waiting to be picked up (a paused queue keeps them here too), prioritized ones included. */
|
|
23
|
+
waiting: number;
|
|
24
|
+
active: number;
|
|
25
|
+
/** Delayed: scheduled for later, or waiting out a retry backoff. */
|
|
26
|
+
delayed: number;
|
|
27
|
+
/** Failed jobs still kept (each queue keeps the last 5000). */
|
|
28
|
+
failed: number;
|
|
29
|
+
/** Of those, the ones that failed in the last 24 hours (by `finishedOn`). */
|
|
30
|
+
failedLastDay: number;
|
|
31
|
+
}
|
|
32
|
+
/** A source the answer could not read right now (its numbers are null). */
|
|
33
|
+
export type AdminHealthSource = "redis" | "database";
|
|
34
|
+
/**
|
|
35
|
+
* The admin overview's 运行状况 (`GET /api/v1/admin/overview/health`, admin overview spec §5.2,
|
|
36
|
+
* §6.2). Each source degrades on its own: when Redis does not answer, `replicas`, `roles`,
|
|
37
|
+
* `queues`, `channels.online` / `.offline` and `devices.online` are null; when PostgreSQL does
|
|
38
|
+
* not, the channel, device and sandbox numbers read from it are null; `unavailable` names them.
|
|
39
|
+
*/
|
|
40
|
+
export interface AdminHealth {
|
|
41
|
+
kernel: {
|
|
42
|
+
version: string;
|
|
43
|
+
};
|
|
44
|
+
/** Live replicas (presence beat within its window), oldest start first. */
|
|
45
|
+
replicas: AdminHealthReplica[] | null;
|
|
46
|
+
/** Live replicas per role (a replica with several roles counts in each). */
|
|
47
|
+
roles: {
|
|
48
|
+
api: number;
|
|
49
|
+
worker: number;
|
|
50
|
+
gateway: number;
|
|
51
|
+
} | null;
|
|
52
|
+
queues: AdminHealthQueue[] | null;
|
|
53
|
+
/**
|
|
54
|
+
* Channel connections of orgs not deleted (deleted connections are not counted):
|
|
55
|
+
* - `online`: long connections held by a live gateway replica now (the replicas' own counts);
|
|
56
|
+
* - `offline`: active long-connection connections no replica holds (`max(0, active − online)`):
|
|
57
|
+
* waiting for a gateway, backing off after a failure, or their plugin is off in the org;
|
|
58
|
+
* - `webhook`: active connections in webhook mode (callbacks, nothing to hold);
|
|
59
|
+
* - `disabled` / `error`: connections in that status, whatever their mode.
|
|
60
|
+
*/
|
|
61
|
+
channels: {
|
|
62
|
+
online: number | null;
|
|
63
|
+
offline: number | null;
|
|
64
|
+
webhook: number | null;
|
|
65
|
+
disabled: number | null;
|
|
66
|
+
error: number | null;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* `local`: local devices (not deleted, org not deleted); `online`: the devices whose connection a
|
|
70
|
+
* live replica holds (lease held), at most `local`.
|
|
71
|
+
*/
|
|
72
|
+
devices: {
|
|
73
|
+
online: number | null;
|
|
74
|
+
local: number | null;
|
|
75
|
+
};
|
|
76
|
+
/** Sandbox instances by status, and running ones per provider (most first). */
|
|
77
|
+
sandboxes: {
|
|
78
|
+
running: number;
|
|
79
|
+
suspended: number;
|
|
80
|
+
byProvider: {
|
|
81
|
+
provider: string;
|
|
82
|
+
running: number;
|
|
83
|
+
}[];
|
|
84
|
+
} | null;
|
|
85
|
+
unavailable: AdminHealthSource[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Reads the platform's health for the admin overview: the live replicas (replica presence), the
|
|
89
|
+
* kernel queues (BullMQ counts plus a ZCOUNT of each failed set over the last 24 hours, the set's
|
|
90
|
+
* score being `finishedOn`), and channel, device and sandbox counts (grouped PostgreSQL counts,
|
|
91
|
+
* plus the connections the live replicas report holding). Not cached: it is cheap, and stale health
|
|
92
|
+
* misleads. Reads the shared queue instances (no connection per request).
|
|
93
|
+
*/
|
|
94
|
+
export declare class AdminHealthService {
|
|
95
|
+
private readonly presence;
|
|
96
|
+
private readonly db;
|
|
97
|
+
private readonly redis;
|
|
98
|
+
private readonly log;
|
|
99
|
+
private readonly queues;
|
|
100
|
+
constructor(presence: Pick<ReplicaPresence, "live">, db: KernelDb, redis: Pick<Redis, "zcount" | "status">, log: Pick<KernelLogger, "warn">, runs: Queue, subagentRuns: Queue, channels: Queue, maintenance: Queue, pluginScope: Queue, titles: Queue, usageReport: Queue, evals: Queue);
|
|
101
|
+
read(): Promise<AdminHealth>;
|
|
102
|
+
private readRedis;
|
|
103
|
+
private readDb;
|
|
104
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { getQueueToken } from "@nestjs/bullmq";
|
|
11
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
12
|
+
import { sql } from "kysely";
|
|
13
|
+
import { PLUGIN_SCOPE_QUEUE } from "../catalog/scope-sync.js";
|
|
14
|
+
import { CHANNEL_QUEUE_NAME } from "../channels/jobs.js";
|
|
15
|
+
import { TITLE_QUEUE } from "../conversations/titles.js";
|
|
16
|
+
import { EVAL_QUEUE_NAME } from "../evals/jobs.js";
|
|
17
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
18
|
+
import { ReplicaPresence } from "../infra/presence/replica-presence.js";
|
|
19
|
+
import { KERNEL_LOGGER, REDIS } from "../infra/tokens.js";
|
|
20
|
+
import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
|
|
21
|
+
import { RUN_QUEUE_NAME, SUBAGENT_RUN_QUEUE_NAME } from "../runtime/run-jobs.js";
|
|
22
|
+
import { USAGE_REPORT_QUEUE } from "../runtime/usage-report-jobs.js";
|
|
23
|
+
import { KERNEL_VERSION } from "../version.js";
|
|
24
|
+
/** The kernel's own queues, in the order the overview lists them (extension queues are not read). */
|
|
25
|
+
export const KERNEL_QUEUE_NAMES = [
|
|
26
|
+
RUN_QUEUE_NAME,
|
|
27
|
+
SUBAGENT_RUN_QUEUE_NAME,
|
|
28
|
+
CHANNEL_QUEUE_NAME,
|
|
29
|
+
MAINTENANCE_QUEUE_NAME,
|
|
30
|
+
PLUGIN_SCOPE_QUEUE,
|
|
31
|
+
TITLE_QUEUE,
|
|
32
|
+
USAGE_REPORT_QUEUE,
|
|
33
|
+
EVAL_QUEUE_NAME,
|
|
34
|
+
];
|
|
35
|
+
/** How long each source's reads may take before the answer marks it unavailable. */
|
|
36
|
+
export const HEALTH_READ_TIMEOUT_MS = 3_000;
|
|
37
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
38
|
+
/** Rejects when `p` does not settle within `ms` (a down Redis makes commands wait, not fail). */
|
|
39
|
+
function within(p, ms) {
|
|
40
|
+
let timer;
|
|
41
|
+
const timeout = new Promise((_, reject) => {
|
|
42
|
+
timer = setTimeout(() => reject(new Error(`no answer within ${ms} ms`)), ms);
|
|
43
|
+
timer.unref?.();
|
|
44
|
+
});
|
|
45
|
+
p.catch(() => { }); // a late rejection after the timeout is not unhandled
|
|
46
|
+
return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reads the platform's health for the admin overview: the live replicas (replica presence), the
|
|
50
|
+
* kernel queues (BullMQ counts plus a ZCOUNT of each failed set over the last 24 hours, the set's
|
|
51
|
+
* score being `finishedOn`), and channel, device and sandbox counts (grouped PostgreSQL counts,
|
|
52
|
+
* plus the connections the live replicas report holding). Not cached: it is cheap, and stale health
|
|
53
|
+
* misleads. Reads the shared queue instances (no connection per request).
|
|
54
|
+
*/
|
|
55
|
+
let AdminHealthService = class AdminHealthService {
|
|
56
|
+
presence;
|
|
57
|
+
db;
|
|
58
|
+
redis;
|
|
59
|
+
log;
|
|
60
|
+
queues;
|
|
61
|
+
constructor(presence, db, redis, log, runs, subagentRuns, channels, maintenance, pluginScope, titles, usageReport, evals) {
|
|
62
|
+
this.presence = presence;
|
|
63
|
+
this.db = db;
|
|
64
|
+
this.redis = redis;
|
|
65
|
+
this.log = log;
|
|
66
|
+
// the order of KERNEL_QUEUE_NAMES
|
|
67
|
+
this.queues = [
|
|
68
|
+
runs,
|
|
69
|
+
subagentRuns,
|
|
70
|
+
channels,
|
|
71
|
+
maintenance,
|
|
72
|
+
pluginScope,
|
|
73
|
+
titles,
|
|
74
|
+
usageReport,
|
|
75
|
+
evals,
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
async read() {
|
|
79
|
+
const [r, d] = await Promise.allSettled([
|
|
80
|
+
within(this.readRedis(), HEALTH_READ_TIMEOUT_MS),
|
|
81
|
+
// a query left running past the limit finishes on its pooled connection
|
|
82
|
+
within(this.readDb(), HEALTH_READ_TIMEOUT_MS),
|
|
83
|
+
]);
|
|
84
|
+
const unavailable = [];
|
|
85
|
+
const redis = r.status === "fulfilled" ? r.value : null;
|
|
86
|
+
if (r.status === "rejected") {
|
|
87
|
+
unavailable.push("redis");
|
|
88
|
+
this.log.warn("admin health: cannot read Redis", { err: r.reason });
|
|
89
|
+
}
|
|
90
|
+
const db = d.status === "fulfilled" ? d.value : null;
|
|
91
|
+
if (d.status === "rejected") {
|
|
92
|
+
unavailable.push("database");
|
|
93
|
+
this.log.warn("admin health: cannot read the database", { err: d.reason });
|
|
94
|
+
}
|
|
95
|
+
const replicas = redis?.replicas ?? null;
|
|
96
|
+
const sum = (k) => replicas === null ? null : replicas.reduce((n, x) => n + x[k], 0);
|
|
97
|
+
const online = sum("channelsHeld");
|
|
98
|
+
const channelCount = (f) => db === null ? null : db.channels.filter(f).reduce((n, c) => n + c.n, 0);
|
|
99
|
+
const longActive = channelCount((c) => c.status === "active" && c.mode === "long_connection");
|
|
100
|
+
// Each replica counts only the devices whose lease it holds, so a device is online on at most
|
|
101
|
+
// one; the clamp covers a lease handover in flight and devices deleted while still connected
|
|
102
|
+
const connected = sum("devicesConnected");
|
|
103
|
+
const devicesOnline = connected === null || db === null ? connected : Math.min(connected, db.localDevices);
|
|
104
|
+
let sandboxes = null;
|
|
105
|
+
if (db !== null) {
|
|
106
|
+
const byProvider = new Map();
|
|
107
|
+
let running = 0;
|
|
108
|
+
let suspended = 0;
|
|
109
|
+
for (const i of db.instances) {
|
|
110
|
+
if (i.status === "suspended")
|
|
111
|
+
suspended += i.n;
|
|
112
|
+
if (i.status !== "running")
|
|
113
|
+
continue;
|
|
114
|
+
running += i.n;
|
|
115
|
+
if (i.provider !== null) {
|
|
116
|
+
byProvider.set(i.provider, (byProvider.get(i.provider) ?? 0) + i.n);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
sandboxes = {
|
|
120
|
+
running,
|
|
121
|
+
suspended,
|
|
122
|
+
byProvider: [...byProvider]
|
|
123
|
+
.map(([provider, n]) => ({ provider, running: n }))
|
|
124
|
+
.sort((a, b) => b.running - a.running || a.provider.localeCompare(b.provider)),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
kernel: { version: KERNEL_VERSION },
|
|
129
|
+
replicas: replicas?.map((x) => ({
|
|
130
|
+
id: x.id,
|
|
131
|
+
roles: x.roles,
|
|
132
|
+
version: x.version,
|
|
133
|
+
startedAt: x.startedAt,
|
|
134
|
+
httpReplicaId: x.httpReplicaId,
|
|
135
|
+
})) ?? null,
|
|
136
|
+
roles: replicas === null
|
|
137
|
+
? null
|
|
138
|
+
: {
|
|
139
|
+
api: replicas.filter((x) => x.roles.includes("api")).length,
|
|
140
|
+
worker: replicas.filter((x) => x.roles.includes("worker")).length,
|
|
141
|
+
gateway: replicas.filter((x) => x.roles.includes("gateway")).length,
|
|
142
|
+
},
|
|
143
|
+
queues: redis?.queues ?? null,
|
|
144
|
+
channels: {
|
|
145
|
+
online,
|
|
146
|
+
offline: online === null || longActive === null ? null : Math.max(0, longActive - online),
|
|
147
|
+
webhook: channelCount((c) => c.status === "active" && c.mode === "webhook"),
|
|
148
|
+
disabled: channelCount((c) => c.status === "disabled"),
|
|
149
|
+
error: channelCount((c) => c.status === "error"),
|
|
150
|
+
},
|
|
151
|
+
devices: { online: devicesOnline, local: db?.localDevices ?? null },
|
|
152
|
+
sandboxes,
|
|
153
|
+
unavailable,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async readRedis() {
|
|
157
|
+
// Fail fast while a connection is down: ioredis queues commands until it reconnects (the
|
|
158
|
+
// BullMQ connections retry forever, maxRetriesPerRequest null), so with the page polling every
|
|
159
|
+
// 30 s the reads would pile up in the offline queues instead of reporting Redis unavailable
|
|
160
|
+
if (this.redis.status !== "ready")
|
|
161
|
+
throw new Error(`redis is ${this.redis.status}`);
|
|
162
|
+
for (const q of this.queues) {
|
|
163
|
+
const status = (await q.getBackend().client).status;
|
|
164
|
+
if (status !== "ready")
|
|
165
|
+
throw new Error(`queue ${q.name} connection is ${status}`);
|
|
166
|
+
}
|
|
167
|
+
const since = Date.now() - DAY_MS;
|
|
168
|
+
const [replicas, queues] = await Promise.all([
|
|
169
|
+
this.presence.live(),
|
|
170
|
+
Promise.all(this.queues.map(async (q, i) => {
|
|
171
|
+
const [c, failedLastDay] = await Promise.all([
|
|
172
|
+
q.getJobCounts("wait", "prioritized", "active", "delayed", "failed"),
|
|
173
|
+
// the failed set is scored by finishedOn (pinned by the health test)
|
|
174
|
+
this.redis.zcount(q.toKey("failed"), since, "+inf"),
|
|
175
|
+
]);
|
|
176
|
+
return {
|
|
177
|
+
name: KERNEL_QUEUE_NAMES[i],
|
|
178
|
+
waiting: (c.wait ?? 0) + (c.prioritized ?? 0),
|
|
179
|
+
active: c.active ?? 0,
|
|
180
|
+
delayed: c.delayed ?? 0,
|
|
181
|
+
failed: c.failed ?? 0,
|
|
182
|
+
failedLastDay,
|
|
183
|
+
};
|
|
184
|
+
})),
|
|
185
|
+
]);
|
|
186
|
+
return { replicas, queues };
|
|
187
|
+
}
|
|
188
|
+
async readDb() {
|
|
189
|
+
const db = this.db.db;
|
|
190
|
+
const [channels, devices, instances] = await Promise.all([
|
|
191
|
+
db
|
|
192
|
+
.selectFrom("channel_connections as c")
|
|
193
|
+
.innerJoin("orgs as o", "o.id", "c.org_id")
|
|
194
|
+
.select(["c.status as status", "c.inbound_mode as mode"])
|
|
195
|
+
.select(sql `count(*)`.as("n"))
|
|
196
|
+
.where("c.deleted_at", "is", null)
|
|
197
|
+
.where("o.deleted_at", "is", null)
|
|
198
|
+
.groupBy(["c.status", "c.inbound_mode"])
|
|
199
|
+
.execute(),
|
|
200
|
+
db
|
|
201
|
+
.selectFrom("devices as d")
|
|
202
|
+
.innerJoin("orgs as o", "o.id", "d.org_id")
|
|
203
|
+
.select(sql `count(*)`.as("n"))
|
|
204
|
+
.where("d.source", "=", "local")
|
|
205
|
+
.where("d.deleted_at", "is", null)
|
|
206
|
+
.where("o.deleted_at", "is", null)
|
|
207
|
+
.executeTakeFirst(),
|
|
208
|
+
// the partial indexes on device_instances (running / live) serve these statuses; an
|
|
209
|
+
// instance of a deleted org is left out like its channels and devices
|
|
210
|
+
db
|
|
211
|
+
.selectFrom("device_instances as i")
|
|
212
|
+
.innerJoin("orgs as o", "o.id", "i.org_id")
|
|
213
|
+
.select(["i.provider as provider", "i.status as status"])
|
|
214
|
+
.select(sql `count(*)`.as("n"))
|
|
215
|
+
.where("i.status", "in", ["running", "suspended"])
|
|
216
|
+
.where("o.deleted_at", "is", null)
|
|
217
|
+
.groupBy(["i.provider", "i.status"])
|
|
218
|
+
.execute(),
|
|
219
|
+
]);
|
|
220
|
+
return {
|
|
221
|
+
channels: channels.map((c) => ({ status: c.status, mode: c.mode, n: Number(c.n) })),
|
|
222
|
+
localDevices: Number(devices?.n ?? 0),
|
|
223
|
+
instances: instances.map((i) => ({
|
|
224
|
+
provider: i.provider,
|
|
225
|
+
status: i.status,
|
|
226
|
+
n: Number(i.n),
|
|
227
|
+
})),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
AdminHealthService = __decorate([
|
|
232
|
+
Injectable(),
|
|
233
|
+
__param(0, Inject(ReplicaPresence)),
|
|
234
|
+
__param(1, Inject(KernelDb)),
|
|
235
|
+
__param(2, Inject(REDIS)),
|
|
236
|
+
__param(3, Inject(KERNEL_LOGGER)),
|
|
237
|
+
__param(4, Inject(getQueueToken(RUN_QUEUE_NAME))),
|
|
238
|
+
__param(5, Inject(getQueueToken(SUBAGENT_RUN_QUEUE_NAME))),
|
|
239
|
+
__param(6, Inject(getQueueToken(CHANNEL_QUEUE_NAME))),
|
|
240
|
+
__param(7, Inject(getQueueToken(MAINTENANCE_QUEUE_NAME))),
|
|
241
|
+
__param(8, Inject(getQueueToken(PLUGIN_SCOPE_QUEUE))),
|
|
242
|
+
__param(9, Inject(getQueueToken(TITLE_QUEUE))),
|
|
243
|
+
__param(10, Inject(getQueueToken(USAGE_REPORT_QUEUE))),
|
|
244
|
+
__param(11, Inject(getQueueToken(EVAL_QUEUE_NAME)))
|
|
245
|
+
], AdminHealthService);
|
|
246
|
+
export { AdminHealthService };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import { type AdminHealth, AdminHealthService } from "./health.service.js";
|
|
3
|
+
import { UsageQuery } from "./overview.schemas.js";
|
|
4
|
+
import { type AdminAttention, AdminOverviewService, type AdminOverviewStats } from "./overview.service.js";
|
|
5
|
+
import { AdminUsageService, type AdminUsageWeek } from "./usage.service.js";
|
|
6
|
+
/**
|
|
7
|
+
* The admin console overview (`/api/v1/admin/overview*`, admin overview spec §5.2): one read per
|
|
8
|
+
* block, each failing on its own. Super admins only; service tokens 403; reads are not audited
|
|
9
|
+
* (decision D5).
|
|
10
|
+
*/
|
|
11
|
+
export declare class AdminOverviewController {
|
|
12
|
+
private readonly overview;
|
|
13
|
+
private readonly health;
|
|
14
|
+
private readonly usage;
|
|
15
|
+
constructor(overview: AdminOverviewService, health: AdminHealthService, usage: AdminUsageService);
|
|
16
|
+
/** The numbers: orgs, accounts and everything waiting for a super admin. */
|
|
17
|
+
stats(): Promise<AdminOverviewStats>;
|
|
18
|
+
/** Extensions, plugin updates and model providers needing a look (reason categories only). */
|
|
19
|
+
attention(): Promise<AdminAttention>;
|
|
20
|
+
/**
|
|
21
|
+
* How the platform runs: live replicas by role, kernel queue counts, channel, device and sandbox
|
|
22
|
+
* counts. Not cached; a source that does not answer is named in `unavailable`.
|
|
23
|
+
*/
|
|
24
|
+
readHealth(): Promise<AdminHealth>;
|
|
25
|
+
/**
|
|
26
|
+
* Runs, tokens and estimated cost per calendar day in `tz` over today and the 6 days before,
|
|
27
|
+
* and the top 10 orgs. Cached 300 s (Redis, every replica); a timeout is
|
|
28
|
+
* `admin.usage_unavailable` (503).
|
|
29
|
+
*/
|
|
30
|
+
readUsage(q: z.infer<typeof UsageQuery>): Promise<AdminUsageWeek>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Controller, Get, Inject } from "@nestjs/common";
|
|
11
|
+
import { SuperAdmin } from "../identity/principal.js";
|
|
12
|
+
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
13
|
+
import { ZQuery } from "../infra/validation/zod-params.js";
|
|
14
|
+
import { AdminHealthService } from "./health.service.js";
|
|
15
|
+
import { UsageQuery } from "./overview.schemas.js";
|
|
16
|
+
import { AdminOverviewService, } from "./overview.service.js";
|
|
17
|
+
import { AdminUsageService } from "./usage.service.js";
|
|
18
|
+
class UsageQueryDto extends createZodDto(UsageQuery, "AdminUsageQuery") {
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The admin console overview (`/api/v1/admin/overview*`, admin overview spec §5.2): one read per
|
|
22
|
+
* block, each failing on its own. Super admins only; service tokens 403; reads are not audited
|
|
23
|
+
* (decision D5).
|
|
24
|
+
*/
|
|
25
|
+
let AdminOverviewController = class AdminOverviewController {
|
|
26
|
+
overview;
|
|
27
|
+
health;
|
|
28
|
+
usage;
|
|
29
|
+
constructor(overview, health, usage) {
|
|
30
|
+
this.overview = overview;
|
|
31
|
+
this.health = health;
|
|
32
|
+
this.usage = usage;
|
|
33
|
+
}
|
|
34
|
+
/** The numbers: orgs, accounts and everything waiting for a super admin. */
|
|
35
|
+
stats() {
|
|
36
|
+
return this.overview.stats();
|
|
37
|
+
}
|
|
38
|
+
/** Extensions, plugin updates and model providers needing a look (reason categories only). */
|
|
39
|
+
attention() {
|
|
40
|
+
return this.overview.attention();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* How the platform runs: live replicas by role, kernel queue counts, channel, device and sandbox
|
|
44
|
+
* counts. Not cached; a source that does not answer is named in `unavailable`.
|
|
45
|
+
*/
|
|
46
|
+
readHealth() {
|
|
47
|
+
return this.health.read();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Runs, tokens and estimated cost per calendar day in `tz` over today and the 6 days before,
|
|
51
|
+
* and the top 10 orgs. Cached 300 s (Redis, every replica); a timeout is
|
|
52
|
+
* `admin.usage_unavailable` (503).
|
|
53
|
+
*/
|
|
54
|
+
readUsage(q) {
|
|
55
|
+
return this.usage.week(q.tz);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
__decorate([
|
|
59
|
+
Get()
|
|
60
|
+
], AdminOverviewController.prototype, "stats", null);
|
|
61
|
+
__decorate([
|
|
62
|
+
Get("attention")
|
|
63
|
+
], AdminOverviewController.prototype, "attention", null);
|
|
64
|
+
__decorate([
|
|
65
|
+
Get("health")
|
|
66
|
+
], AdminOverviewController.prototype, "readHealth", null);
|
|
67
|
+
__decorate([
|
|
68
|
+
Get("usage"),
|
|
69
|
+
__param(0, ZQuery(UsageQueryDto))
|
|
70
|
+
], AdminOverviewController.prototype, "readUsage", null);
|
|
71
|
+
AdminOverviewController = __decorate([
|
|
72
|
+
Controller("api/v1/admin/overview"),
|
|
73
|
+
SuperAdmin(),
|
|
74
|
+
__param(0, Inject(AdminOverviewService)),
|
|
75
|
+
__param(1, Inject(AdminHealthService)),
|
|
76
|
+
__param(2, Inject(AdminUsageService))
|
|
77
|
+
], AdminOverviewController);
|
|
78
|
+
export { AdminOverviewController };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { RouteSchemas } from "../platform/route-schemas.js";
|
|
3
|
+
/** `GET /api/v1/admin/overview/usage`: the reader's zone; days are calendar days in it (spec §5.2). */
|
|
4
|
+
export declare const UsageQuery: z.ZodObject<{
|
|
5
|
+
tz: z.ZodString;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
/** The admin overview's routes (admin overview spec §5.2): reads, only usage takes input. */
|
|
8
|
+
export declare const OVERVIEW_ROUTES: readonly RouteSchemas[];
|