persistent notifications + save scripts not tied to local storage.
This commit is contained in:
@@ -41,6 +41,7 @@ type HttpDispatchDeps<
|
||||
handleAuthRoutes: RouteHandlerWithServer<TServer>;
|
||||
handleNotificationsConfigRoutes: RouteHandlerWithServerDeps<TServer, TNotificationsConfig>;
|
||||
handleAutoScriptsRoutes: RouteHandler;
|
||||
handleSavedScriptsRoutes: RouteHandler;
|
||||
handleAutoDeployRoutes: RouteHandlerWithDeps<TAutoDeploy>;
|
||||
handleEnrollmentRoutes: RouteHandler;
|
||||
handleChatRoutes: RouteHandler;
|
||||
@@ -131,6 +132,9 @@ export function createHttpFetchHandler<
|
||||
const autoScriptsResponse = await deps.handleAutoScriptsRoutes(req, url);
|
||||
if (autoScriptsResponse) return autoScriptsResponse;
|
||||
|
||||
const savedScriptsResponse = await deps.handleSavedScriptsRoutes(req, url);
|
||||
if (savedScriptsResponse) return savedScriptsResponse;
|
||||
|
||||
const autoDeployResponse = await deps.handleAutoDeployRoutes(req, url, deps.routeDeps.autoDeploy);
|
||||
if (autoDeployResponse) return autoDeployResponse;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import {
|
||||
getNotificationScreenshot,
|
||||
saveNotificationScreenshot,
|
||||
updateNotificationScreenshotId,
|
||||
getAllPushSubscriptions,
|
||||
deletePushSubscription,
|
||||
type NotificationScreenshotRecord,
|
||||
@@ -133,7 +134,6 @@ export function takePendingNotificationScreenshot(
|
||||
}
|
||||
|
||||
export function storeNotificationScreenshot(
|
||||
notificationHistory: NotificationRecord[],
|
||||
pending: PendingNotificationScreenshot,
|
||||
bytes: Uint8Array,
|
||||
format: string,
|
||||
@@ -154,10 +154,7 @@ export function storeNotificationScreenshot(
|
||||
bytes,
|
||||
});
|
||||
|
||||
const record = notificationHistory.find((item) => item.id === pending.notificationId);
|
||||
if (record) {
|
||||
record.screenshotId = screenshotId;
|
||||
}
|
||||
updateNotificationScreenshotId(pending.notificationId, screenshotId);
|
||||
}
|
||||
|
||||
async function deliverToUserWebhook(
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { authenticateRequest } from "../../auth";
|
||||
import {
|
||||
listSavedScriptsForUser,
|
||||
saveSavedScript,
|
||||
deleteSavedScript,
|
||||
} from "../../db";
|
||||
import { ALLOWED_SCRIPT_TYPES } from "../validation-constants";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export async function handleSavedScriptsRoutes(
|
||||
req: Request,
|
||||
url: URL,
|
||||
): Promise<Response | null> {
|
||||
if (req.method === "GET" && url.pathname === "/api/saved-scripts") {
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: "Not authenticated" }), {
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({ items: listSavedScriptsForUser(user.userId) });
|
||||
}
|
||||
|
||||
if (req.method === "POST" && url.pathname === "/api/saved-scripts") {
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: "Not authenticated" }), {
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
let body: any = {};
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
||||
}
|
||||
|
||||
const name = String(body?.name || "").trim();
|
||||
const content = String(body?.content || "");
|
||||
const scriptTypeRaw = String(body?.scriptType || "powershell").trim();
|
||||
const id = body?.id ? String(body.id) : uuidv4();
|
||||
|
||||
if (!name) {
|
||||
return Response.json({ error: "Name is required" }, { status: 400 });
|
||||
}
|
||||
if (!content.trim()) {
|
||||
return Response.json({ error: "Script content is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const scriptType = ALLOWED_SCRIPT_TYPES.has(scriptTypeRaw)
|
||||
? scriptTypeRaw
|
||||
: "powershell";
|
||||
|
||||
const item = saveSavedScript(id, user.userId, name, content, scriptType);
|
||||
return Response.json({ ok: true, item });
|
||||
}
|
||||
|
||||
const savedScriptMatch = url.pathname.match(/^\/api\/saved-scripts\/(.+)$/);
|
||||
if (savedScriptMatch && req.method === "DELETE") {
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: "Not authenticated" }), {
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const scriptId = decodeURIComponent(savedScriptMatch[1]);
|
||||
const deleted = deleteSavedScript(user.userId, scriptId);
|
||||
if (!deleted) {
|
||||
return Response.json({ error: "Not found" }, { status: 404 });
|
||||
}
|
||||
return Response.json({ ok: true });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { ServerWebSocket } from "bun";
|
||||
import { encode as msgpackEncode } from "@msgpack/msgpack";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import * as clientManager from "../clientManager";
|
||||
import { saveNotification, getNotificationHistory } from "../db";
|
||||
import { logger } from "../logger";
|
||||
import { metrics } from "../metrics";
|
||||
import { encodeMessage } from "../protocol";
|
||||
@@ -47,7 +48,6 @@ type NotificationConfigShape = {
|
||||
};
|
||||
|
||||
type CreateDeps = {
|
||||
notificationHistory: NotificationRecord[];
|
||||
notificationRate: Map<string, NotificationRateState>;
|
||||
pendingNotificationScreenshots: Map<string, PendingNotificationScreenshot>;
|
||||
pluginLoadedByClient: Map<string, Set<string>>;
|
||||
@@ -241,10 +241,11 @@ export function createNotificationPluginHandlers(deps: CreateDeps) {
|
||||
ws.data.sessionId = sessionId;
|
||||
logger.info(`[notify] viewer connected session=${sessionId} userId=${userId ?? "?"} role=${userRole}`);
|
||||
|
||||
const allHistory = getNotificationHistory(500);
|
||||
const visibleHistory =
|
||||
userRole === "admin"
|
||||
? deps.notificationHistory
|
||||
: deps.notificationHistory.filter(
|
||||
? allHistory
|
||||
: allHistory.filter(
|
||||
(item) =>
|
||||
userId !== undefined &&
|
||||
deps.canUserAccessClient(userId, userRole, item.clientId),
|
||||
@@ -281,12 +282,7 @@ export function createNotificationPluginHandlers(deps: CreateDeps) {
|
||||
ts,
|
||||
};
|
||||
|
||||
deps.notificationHistory.unshift(record);
|
||||
const notificationConfig = deps.getNotificationConfig();
|
||||
const limit = Math.max(50, notificationConfig.historyLimit || 200);
|
||||
if (deps.notificationHistory.length > limit) {
|
||||
deps.notificationHistory.splice(limit);
|
||||
}
|
||||
saveNotification(record);
|
||||
|
||||
requestNotificationScreenshot(info, record);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user