feat: add control panel
This commit is contained in:
@@ -46,6 +46,10 @@ export const PATHS = {
|
||||
drift: env('CASAN_CP_DRIFT', 'docs/output/casan/level5-evidence/09-drift-report.json'),
|
||||
businessKpi: env('CASAN_CP_KPI', 'docs/output/casan/level5-evidence/14-business-kpi-report.json'),
|
||||
benignFp: env('CASAN_CP_BENIGN_FP', 'docs/output/casan/benign-fp-report.json'),
|
||||
scoringReport: env('CASAN_CP_SCORING_REPORT', 'docs/output/casan/phase3-real-run-scoring.md'),
|
||||
approvalInbox: env('CASAN_CP_APPROVAL_INBOX', '.specify/level5/approval-inbox.json'),
|
||||
delegationPolicy: env('CASAN_CP_DELEGATION_POLICY', 'packages/casan-harness/config/delegation-policy.yaml'),
|
||||
selfImprove: env('CASAN_CP_SELF_IMPROVE', 'packages/casan-harness/scripts/bash/self-improve.py'),
|
||||
};
|
||||
|
||||
export const STALE_AFTER_S = Number(process.env.CASAN_DASHBOARD_STALE_S ?? 3600);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { join } from 'node:path';
|
||||
import { APP_ROOT } from './app-root.js';
|
||||
import type { SettingsActor } from '../settings/settings.service.js';
|
||||
|
||||
const RBAC_CLI = join(APP_ROOT, 'packages', 'casan-harness', 'scripts', 'bash', 'rbac-check.py');
|
||||
const LOCAL_ROLES = new Set(['org-admin', 'project-admin', 'approver', 'operator', 'viewer', 'auditor']);
|
||||
|
||||
function firstHeader(value: string | string[] | undefined): string | undefined {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}
|
||||
|
||||
function mapClaim(claim: string): string | null {
|
||||
if (LOCAL_ROLES.has(claim)) return claim;
|
||||
try {
|
||||
return execFileSync('python3', [RBAC_CLI, 'map-claim', '--claim', claim], {
|
||||
cwd: APP_ROOT,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
}).trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function roleFromClaim(raw: string | undefined): string {
|
||||
if (!raw) return 'viewer';
|
||||
const claims = raw.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
|
||||
for (const claim of claims) {
|
||||
const mapped = mapClaim(claim);
|
||||
if (mapped) return mapped;
|
||||
}
|
||||
return 'viewer';
|
||||
}
|
||||
|
||||
export function actorFromHeaders(headers: Record<string, string | string[] | undefined>): SettingsActor {
|
||||
const actor = firstHeader(headers['x-casan-actor'])
|
||||
|| firstHeader(headers['x-auth-request-user'])
|
||||
|| firstHeader(headers['x-forwarded-user'])
|
||||
|| 'local-operator';
|
||||
const roleClaim = firstHeader(headers['x-casan-role'])
|
||||
|| firstHeader(headers['x-casan-groups'])
|
||||
|| firstHeader(headers['x-auth-request-groups'])
|
||||
|| firstHeader(headers['x-forwarded-groups']);
|
||||
return {
|
||||
actor,
|
||||
role: roleFromClaim(roleClaim),
|
||||
project: firstHeader(headers['x-casan-project']) || 'default',
|
||||
tenant: firstHeader(headers['x-casan-tenant']) || 'default',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user