Incident jde otevrit a posunout do dalsiho stavu

Seznam incidentu ukazoval jen titulek a casy, server pritom posilal impact
i diagnostiku. Novy detail /dashboard/incidenty/🆔 co to znamena, ticket,
zdroj, pro spravce platformy cela diagnostika s kopirovanim. Stav jde
posunout (zkoumame, pricina znama, sledujeme, vyreseno), vyreseny vratit
do sledovani. Nove pravo incident.manage, platformni incident meni jen
spravce platformy, zmena jde do auditu. Endpointy GET /incidents/:id
a PATCH /incidents/:id/status, OpenAPI a dokumentace.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-09-09 13:30:46 +02:00
co-authored by Claude Fable 5.1
parent bc6508e1f3
commit 42b3da8303
11 changed files with 376 additions and 6 deletions
+6
View File
@@ -124,6 +124,12 @@ export function listIncidents(tenantIds: string[]): Incident[] {
});
}
/** Incident podle ID. Cizi firmy se chova jako neexistujici, stejne jako u ticketu. */
export function findIncident(id: string, tenantIds: string[]): Incident | undefined {
const incident = incidents.find((item) => item.id === id);
return incident && visibleTo(incident, tenantIds) ? incident : undefined;
}
/** Uz je stejny problem otevreny? Aby deset stejnych chyb nedelalo deset incidentu. */
export function findOpenIncident(source: string, tenantIds: string[]): Incident | undefined {
return incidents.find(
+6
View File
@@ -68,6 +68,12 @@ export const permissionCatalog: PermissionDefinition[] = [
},
{ key: 'action.manage', label: 'Spravovat definice akcí', group: 'Nastavení firmy' },
{ key: 'automation.edit', label: 'Upravovat automatizace', group: 'Nastavení firmy' },
{
key: 'incident.manage',
label: 'Měnit stav incidentu',
group: 'Nastavení firmy',
hint: 'Posouvat incident firmy do dalšího stavu až po vyřešení. Platformní incidenty mění jen správce platformy.',
},
{ key: 'connector.manage', label: 'Spravovat konektory', group: 'Nastavení firmy' },
{ key: 'widget.manage', label: 'Spravovat vlastní widgety', group: 'Nastavení firmy' },
{ key: 'group.manage', label: 'Spravovat skupiny řešitelů', group: 'Nastavení firmy' },
+42
View File
@@ -2274,6 +2274,48 @@ export function buildOpenApiDocument() {
},
},
},
'/api/dashboard/incidents/{id}': {
get: {
tags: ['Dashboard'],
summary: 'Detail incidentu',
description:
'Incident firmy nebo platformni. `detail` (diagnostika) dostane jen spravce platformy, ' +
'ostatni maji null. Cizi incident je 404.',
security: [{ bearerAuth: [] }],
parameters: [idParam, tenantParam],
responses: {
'200': jsonResponse('Incident', { $ref: '#/components/schemas/Incident' }),
'404': { description: 'Incident neexistuje nebo patri jine firme' },
},
},
},
'/api/dashboard/incidents/{id}/status': {
patch: {
tags: ['Dashboard'],
summary: 'Posunout incident do dalsiho stavu',
description:
'Pravo incident.manage za firmu incidentu; platformni incident (bez firmy) meni jen ' +
'spravce platformy. Stav resolved nastavi resolvedAt.',
security: [{ bearerAuth: [] }],
parameters: [idParam, tenantParam],
requestBody: jsonBody({
type: 'object',
required: ['status'],
properties: {
status: {
type: 'string',
enum: ['investigating', 'identified', 'monitoring', 'resolved'],
},
},
}),
responses: {
'200': jsonResponse('Incident', { $ref: '#/components/schemas/Incident' }),
'400': { description: 'Neznamy stav' },
'403': { description: 'Chybi pravo incident.manage' },
'404': { description: 'Incident neexistuje nebo patri jine firme' },
},
},
},
'/api/dashboard/stream': {
get: {
tags: ['Dashboard'],
+63 -1
View File
@@ -39,7 +39,13 @@ import {
} from '../data/dashboardLayouts.js';
import { collectScopes } from '../data/flowScope.js';
import { widgetCatalog } from '../data/widgets.js';
import { createIncident, listIncidents } from '../data/incidentStore.js';
import {
createIncident,
findIncident,
listIncidents,
updateIncidentStatus,
type Incident,
} from '../data/incidentStore.js';
import { getSummary } from '../data/mock.js';
import { isMember, listGroups, listPeople } from '../data/people.js';
import { hasPermission } from '../data/permissions.js';
@@ -145,6 +151,62 @@ dashboardRouter.get('/incidents', (req, res) => {
});
});
/** Stejne pravidlo jako u seznamu: diagnostiku vidi jen spravce platformy. */
function publicIncident(req: Request, incident: Incident): Incident {
return { ...incident, detail: req.user!.platformAdmin ? incident.detail : null };
}
dashboardRouter.get('/incidents/:id', (req, res) => {
const scope = scopeOrDeny(req, res);
if (!scope) return;
const incident = findIncident(req.params.id, scope.tenantIds);
if (!incident) return res.status(404).json({ error: 'not_found', message: 'Incident neexistuje.' });
return res.json(publicIncident(req, incident));
});
const incidentStatusSchema = z.object({
status: z.enum(['investigating', 'identified', 'monitoring', 'resolved']),
});
/**
* Posun incidentu do dalsiho stavu.
*
* Incident firmy meni, kdo ma v te firme `incident.manage`. Platformni incident
* (bez firmy) je nas a meni ho jen spravce platformy - klient by jinak mohl
* "vyresit" vypadek, ktery se tyka vsech.
*/
dashboardRouter.patch('/incidents/:id/status', (req, res) => {
const scope = scopeOrDeny(req, res);
if (!scope) return;
const incident = findIncident(req.params.id, scope.tenantIds);
if (!incident) return res.status(404).json({ error: 'not_found', message: 'Incident neexistuje.' });
const allowed =
req.user!.platformAdmin ||
(incident.tenantId !== null && hasPermission(req.user!, 'incident.manage', incident.tenantId));
if (!allowed) {
return res.status(403).json({ error: 'forbidden', message: 'Stav incidentu nemůžete měnit.' });
}
const parsed = incidentStatusSchema.safeParse(req.body);
if (!parsed.success) return validationError(res, parsed.error);
const updated = updateIncidentStatus(incident.id, parsed.data.status);
if (!updated) return res.status(404).json({ error: 'not_found', message: 'Incident neexistuje.' });
recordAudit({
userId: req.user!.id,
userEmail: req.user!.email,
tenantId: incident.tenantId,
action: 'incident.status',
target: incident.id,
detail: { status: parsed.data.status },
});
return res.json(publicIncident(req, updated));
});
/**
* Hlaseni padu portalu.
*