feat: add control panel

This commit is contained in:
thanhnv
2026-07-08 19:07:35 +09:00
parent a07b15e489
commit 3be9970c15
104 changed files with 3639 additions and 461 deletions
@@ -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',
};
}