92eeeadec0
Previously each AppWidget fetched /api/apps/{id}/history individually
on mount, causing N sequential HTTP requests. Now the board page
server load fetches all app histories in a single Prisma query via
getBatchStatusHistory() and passes them to AppWidget via Svelte
context. AppWidget uses the pre-loaded data immediately with a
fallback fetch for non-board contexts.
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import { error } from '@sveltejs/kit';
|
|
import type { PageServerLoad } from './$types.js';
|
|
import * as boardService from '$lib/server/services/boardService.js';
|
|
import * as appService from '$lib/server/services/appService.js';
|
|
import * as permissionService from '$lib/server/services/permissionService.js';
|
|
import * as userService from '$lib/server/services/userService.js';
|
|
import * as groupService from '$lib/server/services/groupService.js';
|
|
import { EntityType, PermissionLevel, UserRole } from '$lib/utils/constants.js';
|
|
import { isBoardGuestAccessible } from '$lib/server/middleware/guestAccess.js';
|
|
|
|
export const load: PageServerLoad = async ({ params, locals }) => {
|
|
const { boardId } = params;
|
|
const user = locals.user;
|
|
|
|
// Permission check
|
|
if (!user) {
|
|
const isGuest = await isBoardGuestAccessible(boardId);
|
|
if (!isGuest) {
|
|
throw error(401, { message: 'Authentication required' });
|
|
}
|
|
} else if (user.role !== UserRole.ADMIN) {
|
|
const result = await permissionService.checkPermission(
|
|
EntityType.BOARD,
|
|
boardId,
|
|
user.id,
|
|
PermissionLevel.VIEW
|
|
);
|
|
if (!result.hasPermission) {
|
|
const isGuest = await isBoardGuestAccessible(boardId);
|
|
if (!isGuest) {
|
|
throw error(403, { message: 'Insufficient permissions' });
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
// findBoardById includes sections -> widgets -> app -> statuses
|
|
const [board, allApps] = await Promise.all([
|
|
boardService.findBoardById(boardId),
|
|
appService.findAll()
|
|
]);
|
|
|
|
// Determine if user can edit this board
|
|
let canEdit = false;
|
|
if (user) {
|
|
if (user.role === UserRole.ADMIN) {
|
|
canEdit = true;
|
|
} else {
|
|
const editResult = await permissionService.checkPermission(
|
|
EntityType.BOARD,
|
|
boardId,
|
|
user.id,
|
|
PermissionLevel.EDIT
|
|
);
|
|
canEdit = editResult.hasPermission;
|
|
}
|
|
}
|
|
|
|
// Load users and groups for the share dialog (only if user can edit)
|
|
let users: { id: string; name: string }[] = [];
|
|
let groups: { id: string; name: string }[] = [];
|
|
|
|
if (canEdit) {
|
|
const [allUsers, allGroups] = await Promise.all([
|
|
userService.findAll(),
|
|
groupService.findAll()
|
|
]);
|
|
users = allUsers.map((u) => ({
|
|
id: u.id,
|
|
name: u.displayName || u.email
|
|
}));
|
|
groups = allGroups.map((g) => ({
|
|
id: g.id,
|
|
name: g.name
|
|
}));
|
|
}
|
|
|
|
// Batch-load sparkline history for all apps on this board (single query)
|
|
const appIdsOnBoard = board.sections
|
|
.flatMap((s: { widgets: { appId: string | null }[] }) => s.widgets)
|
|
.map((w: { appId: string | null }) => w.appId)
|
|
.filter((id: string | null): id is string => id !== null);
|
|
|
|
const historyMap = appIdsOnBoard.length > 0
|
|
? await appService.getBatchStatusHistory(appIdsOnBoard)
|
|
: new Map();
|
|
|
|
// Serialize the Map to a plain object for the client
|
|
const appHistories: Record<string, { history: { status: string; responseTime: number | null; checkedAt: string }[]; uptimePercent: number }> = {};
|
|
for (const [appId, data] of historyMap) {
|
|
appHistories[appId] = {
|
|
history: data.history.map((h) => ({ ...h, checkedAt: h.checkedAt.toISOString() })),
|
|
uptimePercent: data.uptimePercent
|
|
};
|
|
}
|
|
|
|
return { board, canEdit, allApps, users, groups, appHistories };
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : 'Board not found';
|
|
if (message.includes('not found')) {
|
|
throw error(404, { message: 'Board not found' });
|
|
}
|
|
throw error(500, { message });
|
|
}
|
|
};
|