@naisys/supervisor 3.0.0-beta.10
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/bin/naisys-supervisor +2 -0
- package/client-dist/android-chrome-192x192.png +0 -0
- package/client-dist/android-chrome-512x512.png +0 -0
- package/client-dist/apple-touch-icon.png +0 -0
- package/client-dist/assets/index-CKg0vgt5.css +1 -0
- package/client-dist/assets/index-WzoDF0aQ.js +177 -0
- package/client-dist/assets/naisys-logo-CzoPnn5I.webp +0 -0
- package/client-dist/favicon-16x16.png +0 -0
- package/client-dist/favicon-32x32.png +0 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +49 -0
- package/client-dist/site.webmanifest +22 -0
- package/dist/api-reference.js +54 -0
- package/dist/auth-middleware.js +116 -0
- package/dist/database/hubDb.js +26 -0
- package/dist/database/supervisorDb.js +18 -0
- package/dist/error-helpers.js +13 -0
- package/dist/hateoas.js +61 -0
- package/dist/logger.js +11 -0
- package/dist/route-helpers.js +7 -0
- package/dist/routes/admin.js +209 -0
- package/dist/routes/agentChat.js +194 -0
- package/dist/routes/agentConfig.js +265 -0
- package/dist/routes/agentLifecycle.js +350 -0
- package/dist/routes/agentMail.js +171 -0
- package/dist/routes/agentRuns.js +90 -0
- package/dist/routes/agents.js +236 -0
- package/dist/routes/api.js +52 -0
- package/dist/routes/attachments.js +18 -0
- package/dist/routes/auth.js +103 -0
- package/dist/routes/costs.js +51 -0
- package/dist/routes/hosts.js +296 -0
- package/dist/routes/models.js +152 -0
- package/dist/routes/root.js +56 -0
- package/dist/routes/schemas.js +31 -0
- package/dist/routes/status.js +20 -0
- package/dist/routes/users.js +420 -0
- package/dist/routes/variables.js +103 -0
- package/dist/schema-registry.js +23 -0
- package/dist/services/agentConfigService.js +182 -0
- package/dist/services/agentHostStatusService.js +178 -0
- package/dist/services/agentService.js +291 -0
- package/dist/services/attachmentProxyService.js +131 -0
- package/dist/services/browserSocketService.js +78 -0
- package/dist/services/chatService.js +201 -0
- package/dist/services/configExportService.js +61 -0
- package/dist/services/costsService.js +127 -0
- package/dist/services/hostService.js +156 -0
- package/dist/services/hubConnectionService.js +320 -0
- package/dist/services/logFileService.js +11 -0
- package/dist/services/mailService.js +154 -0
- package/dist/services/modelService.js +92 -0
- package/dist/services/runsService.js +168 -0
- package/dist/services/userService.js +147 -0
- package/dist/services/variableService.js +23 -0
- package/dist/supervisorServer.js +221 -0
- package/package.json +79 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { AgentDetailResponseSchema, AgentListRequestSchema, AgentListResponseSchema, AgentUsernameParamsSchema, CreateAgentConfigRequestSchema, CreateAgentConfigResponseSchema, ErrorResponseSchema, } from "@naisys/supervisor-shared";
|
|
2
|
+
import { hasPermission, requirePermission } from "../auth-middleware.js";
|
|
3
|
+
import { badRequest, notFound } from "../error-helpers.js";
|
|
4
|
+
import { API_PREFIX, collectionLink, schemaLink, selfLink, } from "../hateoas.js";
|
|
5
|
+
import { resolveActions } from "../route-helpers.js";
|
|
6
|
+
import { createAgentConfig } from "../services/agentConfigService.js";
|
|
7
|
+
import { getAgentStatus, isAgentActive, } from "../services/agentHostStatusService.js";
|
|
8
|
+
import { getAgent, getAgents, resolveAgentId, } from "../services/agentService.js";
|
|
9
|
+
function agentActions(username, user, enabled, archived, agentId, hasSpendLimit) {
|
|
10
|
+
const active = agentId ? isAgentActive(agentId) : false;
|
|
11
|
+
const href = `${API_PREFIX}/agents/${username}`;
|
|
12
|
+
return resolveActions([
|
|
13
|
+
{
|
|
14
|
+
rel: "start",
|
|
15
|
+
path: "/start",
|
|
16
|
+
method: "POST",
|
|
17
|
+
title: "Start Agent",
|
|
18
|
+
schema: `${API_PREFIX}/schemas/StartAgent`,
|
|
19
|
+
body: { task: "" },
|
|
20
|
+
permission: "manage_agents",
|
|
21
|
+
disabledWhen: (ctx) => ctx.active
|
|
22
|
+
? "Agent is already running"
|
|
23
|
+
: ctx.archived
|
|
24
|
+
? "Agent is archived"
|
|
25
|
+
: !ctx.enabled
|
|
26
|
+
? "Agent is disabled"
|
|
27
|
+
: null,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
rel: "stop",
|
|
31
|
+
path: "/stop",
|
|
32
|
+
method: "POST",
|
|
33
|
+
title: "Stop Agent",
|
|
34
|
+
permission: "manage_agents",
|
|
35
|
+
disabledWhen: (ctx) => (!ctx.active ? "Agent is not running" : null),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
rel: "disable",
|
|
39
|
+
path: "/disable",
|
|
40
|
+
method: "POST",
|
|
41
|
+
title: "Disable Agent",
|
|
42
|
+
permission: "manage_agents",
|
|
43
|
+
visibleWhen: (ctx) => !ctx.archived && ctx.enabled,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
rel: "enable",
|
|
47
|
+
path: "/enable",
|
|
48
|
+
method: "POST",
|
|
49
|
+
title: "Enable Agent",
|
|
50
|
+
permission: "manage_agents",
|
|
51
|
+
visibleWhen: (ctx) => !ctx.archived && !ctx.enabled && !ctx.active,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
rel: "archive",
|
|
55
|
+
path: "/archive",
|
|
56
|
+
method: "POST",
|
|
57
|
+
title: "Archive Agent",
|
|
58
|
+
permission: "manage_agents",
|
|
59
|
+
visibleWhen: (ctx) => !ctx.archived,
|
|
60
|
+
disabledWhen: (ctx) => ctx.active ? "Stop the agent before archiving" : null,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
rel: "unarchive",
|
|
64
|
+
path: "/unarchive",
|
|
65
|
+
method: "POST",
|
|
66
|
+
title: "Unarchive Agent",
|
|
67
|
+
permission: "manage_agents",
|
|
68
|
+
visibleWhen: (ctx) => ctx.archived,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
rel: "delete",
|
|
72
|
+
method: "DELETE",
|
|
73
|
+
title: "Delete Agent",
|
|
74
|
+
permission: "manage_agents",
|
|
75
|
+
visibleWhen: (ctx) => !ctx.active && ctx.archived,
|
|
76
|
+
hideWithoutPermission: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
rel: "update-config",
|
|
80
|
+
path: "/config",
|
|
81
|
+
method: "PUT",
|
|
82
|
+
title: "Update Agent Config",
|
|
83
|
+
schema: `${API_PREFIX}/schemas/UpdateAgentConfig`,
|
|
84
|
+
permission: "manage_agents",
|
|
85
|
+
visibleWhen: (ctx) => !ctx.archived,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
rel: "set-lead",
|
|
89
|
+
path: "/lead",
|
|
90
|
+
method: "PUT",
|
|
91
|
+
title: "Set Lead Agent",
|
|
92
|
+
schema: `${API_PREFIX}/schemas/SetLeadAgent`,
|
|
93
|
+
body: { leadAgentUsername: "" },
|
|
94
|
+
permission: "manage_agents",
|
|
95
|
+
visibleWhen: (ctx) => !ctx.archived,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
rel: "reset-spend",
|
|
99
|
+
path: "/reset-spend",
|
|
100
|
+
method: "POST",
|
|
101
|
+
title: "Reset Spend",
|
|
102
|
+
permission: "manage_agents",
|
|
103
|
+
visibleWhen: (ctx) => !ctx.archived && ctx.hasSpendLimit,
|
|
104
|
+
},
|
|
105
|
+
], href, { user, active, archived, enabled, hasSpendLimit: hasSpendLimit ?? false });
|
|
106
|
+
}
|
|
107
|
+
function agentLinks(username, config) {
|
|
108
|
+
const links = [
|
|
109
|
+
selfLink(`/agents/${username}`),
|
|
110
|
+
{ rel: "config", href: `${API_PREFIX}/agents/${username}/config` },
|
|
111
|
+
{ rel: "runs", href: `${API_PREFIX}/agents/${username}/runs` },
|
|
112
|
+
collectionLink("agents"),
|
|
113
|
+
];
|
|
114
|
+
if (config?.mailEnabled) {
|
|
115
|
+
links.push({ rel: "mail", href: `${API_PREFIX}/agents/${username}/mail` });
|
|
116
|
+
}
|
|
117
|
+
if (config?.chatEnabled) {
|
|
118
|
+
links.push({ rel: "chat", href: `${API_PREFIX}/agents/${username}/chat` });
|
|
119
|
+
}
|
|
120
|
+
return links;
|
|
121
|
+
}
|
|
122
|
+
export default function agentsRoutes(fastify, _options) {
|
|
123
|
+
// GET / — List agents
|
|
124
|
+
fastify.get("/", {
|
|
125
|
+
schema: {
|
|
126
|
+
description: "List agents with status and metadata",
|
|
127
|
+
tags: ["Agents"],
|
|
128
|
+
querystring: AgentListRequestSchema,
|
|
129
|
+
response: {
|
|
130
|
+
200: AgentListResponseSchema,
|
|
131
|
+
500: ErrorResponseSchema,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
}, async (request, _reply) => {
|
|
135
|
+
const { updatedSince } = request.query;
|
|
136
|
+
const agents = await getAgents(updatedSince);
|
|
137
|
+
const items = agents.map((agent) => ({
|
|
138
|
+
...agent,
|
|
139
|
+
status: getAgentStatus(agent.id),
|
|
140
|
+
}));
|
|
141
|
+
const hasManagePermission = hasPermission(request.supervisorUser, "manage_agents");
|
|
142
|
+
const actions = [
|
|
143
|
+
{
|
|
144
|
+
rel: "create",
|
|
145
|
+
href: `${API_PREFIX}/agents`,
|
|
146
|
+
method: "POST",
|
|
147
|
+
title: "Create Agent",
|
|
148
|
+
schema: `${API_PREFIX}/schemas/CreateAgent`,
|
|
149
|
+
body: { name: "" },
|
|
150
|
+
...(hasManagePermission
|
|
151
|
+
? {}
|
|
152
|
+
: {
|
|
153
|
+
disabled: true,
|
|
154
|
+
disabledReason: "Requires manage_agents permission",
|
|
155
|
+
}),
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
return {
|
|
159
|
+
items,
|
|
160
|
+
timestamp: new Date().toISOString(),
|
|
161
|
+
_links: [selfLink("/agents"), schemaLink("CreateAgent")],
|
|
162
|
+
_linkTemplates: [
|
|
163
|
+
{ rel: "item", hrefTemplate: `${API_PREFIX}/agents/{name}` },
|
|
164
|
+
],
|
|
165
|
+
_actions: actions,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
// POST / — Create agent
|
|
169
|
+
fastify.post("/", {
|
|
170
|
+
preHandler: [requirePermission("manage_agents")],
|
|
171
|
+
schema: {
|
|
172
|
+
description: "Create a new agent with configuration file",
|
|
173
|
+
tags: ["Agents"],
|
|
174
|
+
body: CreateAgentConfigRequestSchema,
|
|
175
|
+
response: {
|
|
176
|
+
200: CreateAgentConfigResponseSchema,
|
|
177
|
+
400: ErrorResponseSchema,
|
|
178
|
+
500: ErrorResponseSchema,
|
|
179
|
+
},
|
|
180
|
+
security: [{ cookieAuth: [] }],
|
|
181
|
+
},
|
|
182
|
+
}, async (request, reply) => {
|
|
183
|
+
try {
|
|
184
|
+
const { name, title } = request.body;
|
|
185
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
186
|
+
return badRequest(reply, "Agent name must contain only alphanumeric characters, hyphens, and underscores");
|
|
187
|
+
}
|
|
188
|
+
const { config } = await createAgentConfig(name, title);
|
|
189
|
+
return {
|
|
190
|
+
success: true,
|
|
191
|
+
message: `Agent '${name}' created successfully`,
|
|
192
|
+
name,
|
|
193
|
+
_links: agentLinks(name, config),
|
|
194
|
+
_actions: agentActions(name, request.supervisorUser, true, false),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
request.log.error(error, "Error in POST /agents route");
|
|
199
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
200
|
+
if (errorMessage.includes("already exists")) {
|
|
201
|
+
return badRequest(reply, errorMessage);
|
|
202
|
+
}
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
// GET /:username — Agent detail with config
|
|
207
|
+
fastify.get("/:username", {
|
|
208
|
+
schema: {
|
|
209
|
+
description: "Get agent detail with configuration",
|
|
210
|
+
tags: ["Agents"],
|
|
211
|
+
params: AgentUsernameParamsSchema,
|
|
212
|
+
response: {
|
|
213
|
+
200: AgentDetailResponseSchema,
|
|
214
|
+
404: ErrorResponseSchema,
|
|
215
|
+
500: ErrorResponseSchema,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}, async (request, reply) => {
|
|
219
|
+
const { username } = request.params;
|
|
220
|
+
const id = resolveAgentId(username);
|
|
221
|
+
if (!id) {
|
|
222
|
+
return notFound(reply, `Agent '${username}' not found`);
|
|
223
|
+
}
|
|
224
|
+
const agent = await getAgent(id);
|
|
225
|
+
if (!agent) {
|
|
226
|
+
return notFound(reply, `Agent '${username}' not found`);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
...agent,
|
|
230
|
+
status: getAgentStatus(id),
|
|
231
|
+
_links: agentLinks(username, agent.config),
|
|
232
|
+
_actions: agentActions(username, request.supervisorUser, agent.enabled ?? false, agent.archived ?? false, id, agent.config?.spendLimitDollars != null),
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { registerAuthMiddleware } from "../auth-middleware.js";
|
|
2
|
+
import adminRoutes from "./admin.js";
|
|
3
|
+
import agentChatRoutes from "./agentChat.js";
|
|
4
|
+
import agentConfigRoutes from "./agentConfig.js";
|
|
5
|
+
import agentLifecycleRoutes from "./agentLifecycle.js";
|
|
6
|
+
import agentMailRoutes from "./agentMail.js";
|
|
7
|
+
import agentRunsRoutes from "./agentRuns.js";
|
|
8
|
+
import agentsRoutes from "./agents.js";
|
|
9
|
+
import attachmentRoutes from "./attachments.js";
|
|
10
|
+
import authRoutes from "./auth.js";
|
|
11
|
+
import costsRoutes from "./costs.js";
|
|
12
|
+
import hostsRoutes from "./hosts.js";
|
|
13
|
+
import modelsRoutes from "./models.js";
|
|
14
|
+
import rootRoutes from "./root.js";
|
|
15
|
+
import schemaRoutes from "./schemas.js";
|
|
16
|
+
import statusRoutes from "./status.js";
|
|
17
|
+
import userRoutes from "./users.js";
|
|
18
|
+
import variablesRoutes from "./variables.js";
|
|
19
|
+
export default async function apiRoutes(fastify, _options) {
|
|
20
|
+
// Register auth middleware for all routes in this scope
|
|
21
|
+
registerAuthMiddleware(fastify);
|
|
22
|
+
// Register root discovery routes
|
|
23
|
+
await fastify.register(rootRoutes);
|
|
24
|
+
// Register auth routes
|
|
25
|
+
await fastify.register(authRoutes);
|
|
26
|
+
// Register schema routes
|
|
27
|
+
await fastify.register(schemaRoutes, { prefix: "/schemas" });
|
|
28
|
+
// Register user routes
|
|
29
|
+
await fastify.register(userRoutes, { prefix: "/users" });
|
|
30
|
+
// Register status routes
|
|
31
|
+
await fastify.register(statusRoutes);
|
|
32
|
+
// Register agents routes
|
|
33
|
+
await fastify.register(agentsRoutes, { prefix: "/agents" });
|
|
34
|
+
await fastify.register(agentLifecycleRoutes, { prefix: "/agents" });
|
|
35
|
+
await fastify.register(agentConfigRoutes, { prefix: "/agents" });
|
|
36
|
+
await fastify.register(agentRunsRoutes, { prefix: "/agents" });
|
|
37
|
+
await fastify.register(agentMailRoutes, { prefix: "/agents" });
|
|
38
|
+
await fastify.register(agentChatRoutes, { prefix: "/agents" });
|
|
39
|
+
// Register hosts routes
|
|
40
|
+
await fastify.register(hostsRoutes, { prefix: "/hosts" });
|
|
41
|
+
// Register models routes
|
|
42
|
+
await fastify.register(modelsRoutes);
|
|
43
|
+
// Register variables routes
|
|
44
|
+
await fastify.register(variablesRoutes, { prefix: "/variables" });
|
|
45
|
+
// Register costs routes
|
|
46
|
+
await fastify.register(costsRoutes, { prefix: "/costs" });
|
|
47
|
+
// Register admin routes
|
|
48
|
+
await fastify.register(adminRoutes, { prefix: "/admin" });
|
|
49
|
+
// Register attachment routes
|
|
50
|
+
await fastify.register(attachmentRoutes, { prefix: "/attachments" });
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { proxyDownloadFromHub } from "../services/attachmentProxyService.js";
|
|
2
|
+
export default function attachmentRoutes(fastify, _options) {
|
|
3
|
+
// GET /:id or /:id/:filename — Download attachment (proxied through hub)
|
|
4
|
+
const handler = async (request, reply) => {
|
|
5
|
+
const publicId = request.params.id;
|
|
6
|
+
if (!publicId) {
|
|
7
|
+
return reply.code(400).send({ error: "Missing attachment ID" });
|
|
8
|
+
}
|
|
9
|
+
await proxyDownloadFromHub(publicId, reply);
|
|
10
|
+
};
|
|
11
|
+
const schema = {
|
|
12
|
+
description: "Download an attachment by ID (proxied from hub)",
|
|
13
|
+
tags: ["Attachments"],
|
|
14
|
+
};
|
|
15
|
+
fastify.get("/:id", { schema }, handler);
|
|
16
|
+
fastify.get("/:id/:filename", { schema }, handler);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { hashToken, SESSION_COOKIE_NAME, sessionCookieOptions, } from "@naisys/common-node";
|
|
2
|
+
import { authenticateAndCreateSession, deleteSession, } from "@naisys/supervisor-database";
|
|
3
|
+
import { AuthUserSchema, ErrorResponseSchema, LoginRequestSchema, LoginResponseSchema, LogoutResponseSchema, } from "@naisys/supervisor-shared";
|
|
4
|
+
import { authCache } from "../auth-middleware.js";
|
|
5
|
+
import { getUserByUsername, getUserPermissions, } from "../services/userService.js";
|
|
6
|
+
let lastLoginRequestTime = 0;
|
|
7
|
+
export default function authRoutes(fastify, _options) {
|
|
8
|
+
const app = fastify.withTypeProvider();
|
|
9
|
+
// LOGIN
|
|
10
|
+
app.post("/auth/login", {
|
|
11
|
+
config: {
|
|
12
|
+
rateLimit: {
|
|
13
|
+
max: 5,
|
|
14
|
+
timeWindow: "1 minute",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
schema: {
|
|
18
|
+
description: "Authenticate with username and password",
|
|
19
|
+
tags: ["Authentication"],
|
|
20
|
+
body: LoginRequestSchema,
|
|
21
|
+
response: {
|
|
22
|
+
200: LoginResponseSchema,
|
|
23
|
+
401: ErrorResponseSchema,
|
|
24
|
+
429: ErrorResponseSchema,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}, async (request, reply) => {
|
|
28
|
+
const currentTime = Date.now();
|
|
29
|
+
if (currentTime - lastLoginRequestTime < 5000) {
|
|
30
|
+
reply.code(429);
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
message: "Too many requests. Please wait before trying again.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
lastLoginRequestTime = currentTime;
|
|
37
|
+
const { username, password } = request.body;
|
|
38
|
+
const authResult = await authenticateAndCreateSession(username, password);
|
|
39
|
+
if (!authResult) {
|
|
40
|
+
reply.code(401);
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
message: "Invalid username or password",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
reply.setCookie(SESSION_COOKIE_NAME, authResult.token, sessionCookieOptions(authResult.expiresAt));
|
|
47
|
+
const user = await getUserByUsername(username);
|
|
48
|
+
const permissions = user ? await getUserPermissions(user.id) : [];
|
|
49
|
+
return {
|
|
50
|
+
user: {
|
|
51
|
+
id: user?.id ?? 0,
|
|
52
|
+
username: authResult.user.username,
|
|
53
|
+
permissions,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
// LOGOUT
|
|
58
|
+
app.post("/auth/logout", {
|
|
59
|
+
schema: {
|
|
60
|
+
description: "Log out and clear session",
|
|
61
|
+
tags: ["Authentication"],
|
|
62
|
+
response: {
|
|
63
|
+
200: LogoutResponseSchema,
|
|
64
|
+
},
|
|
65
|
+
security: [{ cookieAuth: [] }],
|
|
66
|
+
},
|
|
67
|
+
}, async (request, reply) => {
|
|
68
|
+
const token = request.cookies?.[SESSION_COOKIE_NAME];
|
|
69
|
+
// Clear from hub and auth cache
|
|
70
|
+
if (token) {
|
|
71
|
+
const tokenHash = hashToken(token);
|
|
72
|
+
authCache.invalidate(`cookie:${tokenHash}`);
|
|
73
|
+
await deleteSession(tokenHash);
|
|
74
|
+
}
|
|
75
|
+
reply.clearCookie(SESSION_COOKIE_NAME, { path: "/" });
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
message: "Logged out successfully",
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
// ME
|
|
82
|
+
app.get("/auth/me", {
|
|
83
|
+
schema: {
|
|
84
|
+
description: "Get current authenticated user",
|
|
85
|
+
tags: ["Authentication"],
|
|
86
|
+
response: {
|
|
87
|
+
200: AuthUserSchema,
|
|
88
|
+
401: ErrorResponseSchema,
|
|
89
|
+
},
|
|
90
|
+
security: [{ cookieAuth: [] }],
|
|
91
|
+
},
|
|
92
|
+
}, async (request, reply) => {
|
|
93
|
+
if (!request.supervisorUser) {
|
|
94
|
+
reply.code(401);
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
message: "Not authenticated",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return request.supervisorUser;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CostsHistogramRequestSchema, CostsHistogramResponseSchema, ErrorResponseSchema, } from "@naisys/supervisor-shared";
|
|
2
|
+
import { findUserIdsForLead, getCostHistogram, getCostsByAgent, getSpendLimitSettings, } from "../services/costsService.js";
|
|
3
|
+
export default function costsRoutes(fastify, _options) {
|
|
4
|
+
fastify.get("/", {
|
|
5
|
+
schema: {
|
|
6
|
+
description: "Get cost histogram data",
|
|
7
|
+
tags: ["Costs"],
|
|
8
|
+
querystring: CostsHistogramRequestSchema,
|
|
9
|
+
response: {
|
|
10
|
+
200: CostsHistogramResponseSchema,
|
|
11
|
+
500: ErrorResponseSchema,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
}, async (request, reply) => {
|
|
15
|
+
try {
|
|
16
|
+
const { spendLimitDollars, spendLimitHours } = await getSpendLimitSettings();
|
|
17
|
+
const now = new Date();
|
|
18
|
+
const defaultStart = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
19
|
+
const start = request.query.start
|
|
20
|
+
? new Date(request.query.start)
|
|
21
|
+
: defaultStart;
|
|
22
|
+
const end = request.query.end ? new Date(request.query.end) : now;
|
|
23
|
+
const bucketHours = request.query.bucketHours ?? 24;
|
|
24
|
+
if (isNaN(start.getTime()) ||
|
|
25
|
+
isNaN(end.getTime()) ||
|
|
26
|
+
bucketHours <= 0) {
|
|
27
|
+
return reply
|
|
28
|
+
.code(400)
|
|
29
|
+
.send({ success: false, message: "Invalid query parameters" });
|
|
30
|
+
}
|
|
31
|
+
const userIds = request.query.leadUsername
|
|
32
|
+
? await findUserIdsForLead(request.query.leadUsername)
|
|
33
|
+
: undefined;
|
|
34
|
+
const [buckets, byAgent] = await Promise.all([
|
|
35
|
+
getCostHistogram(start, end, bucketHours, userIds),
|
|
36
|
+
getCostsByAgent(start, end, userIds),
|
|
37
|
+
]);
|
|
38
|
+
return {
|
|
39
|
+
spendLimitDollars,
|
|
40
|
+
spendLimitHours,
|
|
41
|
+
buckets,
|
|
42
|
+
byAgent,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : "Failed to fetch costs";
|
|
47
|
+
return reply.code(500).send({ success: false, message });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=costs.js.map
|