feat: implement permissions and actions management
CI / Lint, typecheck, test, build (push) Canceled after 0s
CI / End-to-end tests (push) Canceled after 0s

- Add permissions logic for status options in corrective actions.
- Create tests for permissions functionality.
- Implement queries for user authentication and actions management.
- Develop Actions page to display and manage corrective actions.
- Create authentication pages for login, setup, and password reset.
- Add IncidentDetail page to view incident details and associated actions.
- Implement Settings page for user management and password changes.
This commit is contained in:
jayjennings committed 2026-10-06 11:20:44 -07:00
1 parent 708da6b9f7
commit 3f6dff5f24
94 files changed
+7213 -643

No files matched your search

+55
View File
@@ -0,0 +1,55 @@
// Incident status workflow, shared so the web app can show the same options and reasons the API enforces.
import type { ActionStatus } from './api-types.ts'
import { hasRole, type IncidentStatus, type Role, type Severity } from './enums.ts'
export interface Transition {
from: IncidentStatus
to: IncidentStatus
minRole: Role
label: string
}
export const transitions: Transition[] = [
{ from: 'reported', to: 'triage', minRole: 'supervisor', label: 'Start triage' },
{ from: 'triage', to: 'investigation', minRole: 'supervisor', label: 'Start investigation' },
{ from: 'triage', to: 'actions', minRole: 'supervisor', label: 'Skip to corrective actions' },
{ from: 'triage', to: 'closed', minRole: 'supervisor', label: 'Close: no further action' },
{ from: 'investigation', to: 'actions', minRole: 'supervisor', label: 'Move to corrective actions' },
{ from: 'actions', to: 'investigation', minRole: 'supervisor', label: 'Back to investigation' },
{ from: 'actions', to: 'verification', minRole: 'supervisor', label: 'Send for verification' },
{ from: 'verification', to: 'actions', minRole: 'safety_manager', label: 'Return to actions' },
{ from: 'verification', to: 'closed', minRole: 'safety_manager', label: 'Verify and close' },
{ from: 'closed', to: 'investigation', minRole: 'safety_manager', label: 'Reopen' },
]
export interface WorkflowFacts {
severity: Severity
rootCauseCount: number
actionStatuses: ActionStatus[]
}
/** Why a transition isn't possible yet, or undefined if it is. */
export function transitionBlocker(transition: Transition, facts: WorkflowFacts): string | undefined {
const live = facts.actionStatuses.filter((status) => status !== 'cancelled')
if (transition.from === 'triage' && transition.to === 'closed' && (facts.severity === 'high' || facts.severity === 'critical')) {
return 'High and critical incidents must be investigated before closing'
}
if (transition.to === 'actions' && transition.from === 'investigation' && facts.rootCauseCount === 0) {
return 'Record at least one root cause first'
}
if (transition.to === 'verification') {
if (live.length === 0) return 'Add at least one corrective action first'
if (live.some((status) => status === 'open' || status === 'in_progress')) return 'All corrective actions must be done first'
}
if (transition.from === 'verification' && transition.to === 'closed' && live.some((status) => status !== 'verified')) {
return 'Verify every corrective action first'
}
return undefined
}
/** Transitions from `status` that `role` may perform, with blockers explained. */
export function availableTransitions(status: IncidentStatus, role: Role, facts: WorkflowFacts) {
return transitions
.filter((t) => t.from === status && hasRole(role, t.minRole))
.map((t) => ({ to: t.to, label: t.label, blockedReason: transitionBlocker(t, facts) }))
}