import { readEnginePreferences, useEnginePreferences } from '../../app/engine-preferences'; import { ClientContextHeatmap } from '../context-layer/ContextHeatmap'; import { CONTEXT_CLUSTER_FACET_ID } from '../context-layer/context-cluster-facet'; import { useAssessmentContext } from '../context-layer/use-assessment-context'; import { publishContextReport } from '../../assessment-context/context-report-publication'; import { managementFindingDistribution, managementWorkspaceDeviationKeys } from '../../reporting/management-business-impact'; import { businessImpactControls, BUSINESS_IMPACT_FACET_ID } from '../search-filter/business-impact-controls'; import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from 'react'; import { AssessorApiClient } from '../../api/assessor-api-client'; import { hasSelectedTimeslotScope } from '../../app/work-scope'; import { downloadText } from '../../exports/text-download'; import { browserDownloadAdapter } from '../../platform/browser/browser-download-adapter'; import { OperatingSystemInventory } from './OperatingSystemInventory'; import { EnvironmentResultsButton } from './EnvironmentResultsButton'; import { CloudInformationTile } from './CloudInformationTile'; import { EnvironmentEvidenceTables } from './EnvironmentEvidenceTables'; import { environmentEvidenceForGroup, type EnvironmentEvidenceGroup } from './environment-result-evidence'; import type { GpoBackupStatusRecord } from '../../api/assessor-api-contract'; import { operatingSystemInventory, type OperatingSystemInventoryGroup } from './operating-system-inventory'; import { useApp } from '../../app/AppProvider'; import { EmptyWorkspace } from '../../components/EmptyWorkspace/EmptyWorkspace'; import { DonutMetricCard } from '../../components/DonutMetricCard/DonutMetricCard'; import { DonutSmallArcMarker, donutArcPath, donutPercentageLabel } from '../../components/DonutMetricCard/donut-arc'; import { PanelLoadingState } from '../../components/PanelLoadingState/PanelLoadingState'; import { TriangleIcon } from '../../components/TriangleIcon'; import type { ImportedDataset } from '../../domain/dataset'; import type { EnvironmentObservation, EnvironmentSnapshot } from '../../domain/environment-context'; import type { FindingRecord } from '../../domain/finding'; import type { ImportProfile } from '../../domain/import-profile'; import type { Review } from '../../domain/review'; import { weightedSecurityScore, type SecurityScoreContext, type SecuritySourceScore } from '../../domain/security-scoring'; import { dashboardSecuritySources } from './dashboard-security-score'; import { SECURITY_DOMAIN_FACET_ID, dashboardDomainAssignment, securityDomainCatalog } from './security-domains'; import { ScoringMethodology } from './ScoringMethodology'; import { RiskRegister } from './RiskRegister'; import { deriveDynamicFacets, matchesDynamicFacets, recountFacetsAgainstActiveFilters, sourceScanNavigationSheet, type DynamicFacet, type DynamicFacetFilter, type DynamicFacetValue } from '../search-filter/dynamic-facets'; import { WorkTimeslotControl } from '../search-filter/WorkTimeslotControl'; import { dashboardResultState, dashboardScoreFromStates, type DashboardResultState } from './dashboard-result-state'; export { dashboardResultState } from './dashboard-result-state'; import { AiOverviewPanel, useAiOverview } from './AiOverviewPanel'; import { DEFENDER_FOR_IOT_PROFILE_ID } from '../../domain/defender-for-iot'; import { DefenderVulnerabilityPanel } from '../defender-for-iot/DefenderVulnerabilityPanel'; import { defenderModelForDataset } from '../defender-for-iot/defender-dataset-model'; const assessorApi = new AssessorApiClient(); export { dashboardDomainAssignment, dashboardDomainRecords } from './security-domains'; interface DashboardPanelProps { onOpenWork(): void; } function shareStyle(value: number): CSSProperties { return { '--share': `${value}%` } as unknown as CSSProperties; } function share(count: number, total: number): number { return total > 0 ? (count / total) * 100 : 0; } export function dashboardExplicitIoeCount(records: FindingRecord[]): number { return dashboardExplicitIoeRecordIds(records).length; } export function dashboardExplicitIoeRecordIds(records: FindingRecord[]): string[] { return records .filter(record => record.fields.some(field => /^ioes?\s+found$/i.test(field.value.trim()))) .map(record => record.id); } interface DashboardStatusMetricProps { label: string; value: number; percentage: number; tone: 'all' | 'deviation' | 'passed' | 'not-run'; large?: boolean; helper?: string; active?: boolean; onClick?(): void; } function DashboardStatusMetric({ label, value, percentage, tone, large = false, helper, active, onClick }: DashboardStatusMetricProps) { const percentageLabel = donutPercentageLabel(percentage); const content = <>
= 5 ? ' is-compact' : ''}`}>{value.toLocaleString('en-US')}{percentageLabel}
{label} {helper && {helper}}
; const className = `dashboard-status-metric${large ? ' dashboard-status-metric-large' : ''}`; return onClick ? :
{content}
; } interface SegmentedDistributionProps { heading: string; total: number; values: DynamicFacetValue[]; severity?: boolean; formatLabel?(label: string): string; onSelect(value: string): void; } export function dashboardScanSourceLabel(label: string): string { const parts = label.split(/\s+·\s+/); const timestamp = parts.at(-1) ?? ''; if (parts.length < 2 || !/^\d{4}-\d{2}-\d{2}(?:T.*)?$/.test(timestamp) || Number.isNaN(Date.parse(timestamp))) return label; return parts.slice(0, -1).join(' · '); } export interface DashboardRiskTool { id: string; label: string; sourceValues: string[]; count: number; } const dashboardCisCatSeverityValue = 'dashboard-cis-cat-without-severity'; export interface DashboardCisCatSeverityRemainder extends DynamicFacetValue { sourceValues: string[]; } export function dashboardRiskTools(values: DynamicFacetValue[]): DashboardRiskTool[] { const tools = new Map(); values.forEach(value => { const label = dashboardScanSourceLabel(value.label); const id = label.toLocaleLowerCase('en-US'); const existing = tools.get(id); if (existing) { existing.sourceValues.push(value.value); existing.count += value.count; } else tools.set(id, { id, label, sourceValues: [value.value], count: value.count }); }); return [...tools.values()]; } export function dashboardCisCatSeverityRemainder( sourceValues: DynamicFacetValue[], cisCatSeverityValues: DynamicFacetValue[] ): DashboardCisCatSeverityRemainder | undefined { const tool = dashboardRiskTools(sourceValues) .find(item => /^cis-cat(?:\s+pro)?$/i.test(item.label.trim())); if (!tool) return undefined; const classified = cisCatSeverityValues.reduce((sum, value) => sum + value.count, 0); const count = Math.max(0, tool.count - classified); return count > 0 ? { value: dashboardCisCatSeverityValue, label: 'CIS-CAT', count, sourceValues: tool.sourceValues } : undefined; } /** Compact distribution based on the supplied dashboard reference. */ function SegmentedDistribution({ heading, total, values, severity = false, formatLabel = label => label, onSelect }: SegmentedDistributionProps) { return (

{heading}

{values.length === 0 ? (

This scan report carries no such classification.

) : ( <> )}
); } const overviewProperties: Array<[string, string]> = [ ['domain.fqdn', 'Domain'], ['domain.functional_level', 'Domain functional level'], ['forest.functional_level', 'Forest functional level'], ['forest.schema_version', 'Schema version'], ['domain.controller_count', 'Domain controllers'], ['domain.recycle_bin_enabled', 'AD Recycle Bin'], ['backup.last_detected_at', 'Last AD backup'], ['laps.legacy.installation_date', 'Legacy LAPS'], ['laps.windows.installation_date', 'Windows LAPS'], ['logging.wef.configuration_count', 'WEF configurations'], ['kerberos.krbtgt.password_last_changed', 'krbtgt password changed'], ['report.engine_version', 'PingCastle engine'] ]; function displayValue(observation: EnvironmentObservation): string { if (observation.state === 'explicitly-absent') return observation.originalValue === false ? 'Not detected' : 'Not present'; const value = observation.normalizedValue ?? observation.originalValue; if (typeof value === 'boolean') return value ? 'Enabled' : 'Disabled'; if (value === null || value === '') return 'No value'; return String(value); } function useEnvironmentSnapshot(projectId?: string, scanRunIds?: string[]) { const [snapshot, setSnapshot] = useState(); const [failed, setFailed] = useState(false); useEffect(() => { let active = true; setSnapshot(undefined); setFailed(false); if (!projectId) return () => { active = false; }; assessorApi.getEnvironmentSnapshot({ projectId, scanRunIds }) .then(value => { if (active) setSnapshot(value); }) .catch(() => { if (active) setFailed(true); }); return () => { active = false; }; }, [projectId, scanRunIds?.join('|')]); return { snapshot, failed }; } function EnvironmentOverview({ projectId, snapshot, failed }: { projectId?: string; snapshot?: EnvironmentSnapshot; failed: boolean }) { const observations = useMemo(() => { const byProperty = new Map(); snapshot?.observations.forEach(observation => byProperty.set(observation.property, observation)); return overviewProperties .map(([property, label]) => ({ label, observation: byProperty.get(property) })) .filter((item): item is { label: string; observation: EnvironmentObservation } => Boolean(item.observation)); }, [snapshot]); const operatingSystems = useMemo(() => [...new Set(snapshot?.observations .filter(item => item.property === 'operating_system.name') .map(displayValue) ?? [])], [snapshot]); if (!projectId) return null; return (

Directory overview

{snapshot && {snapshot.sections.length.toLocaleString('en-US')} sections}
{!snapshot && !failed &&

Loading Client Information…

} {failed &&

Client Information could not be loaded.

} {snapshot && snapshot.sections.length === 0 &&

This import predates structured scanner-context capture.

} {snapshot && (
{snapshot.observations.length.toLocaleString('en-US')}typed observations {(snapshot.coverage.empty + snapshot.coverage.explicitlyAbsent).toLocaleString('en-US')}empty or absent {snapshot.coverage.restricted.toLocaleString('en-US')}restricted {snapshot.coverage.incomplete.toLocaleString('en-US')}incomplete
)} {snapshot && (observations.length > 0 || operatingSystems.length > 0) && (
{observations.map(({ label, observation }) => (
{label}
{displayValue(observation)}
))} {operatingSystems.length > 0 &&
OS
{operatingSystems.join(', ')}
}
)}
); } interface RiskProfileProps { sourceFacet?: DynamicFacet; categoryFacet?: DynamicFacet; severityFacet?: DynamicFacet; records: FindingRecord[]; profile: ImportProfile; reviews: Record; environmentSnapshot?: EnvironmentSnapshot; onSelectTool(sourceValues: string[]): void; onSelectCategory(value: string, sourceValues: string[]): void; onSelectSeverity(value: string, sourceValues: string[]): void; } type RiskScoreObservation = Pick; const pingCastleRiskCategoryCatalog = [ ['risk.anomalies', 'Anomalies'], ['risk.stale_objects', 'Stale Objects'], ['risk.privileged_accounts', 'Privileged Accounts'], ['risk.trusts', 'Trusts'] ] as const; export function dashboardPingCastleRiskCategories(observations: RiskScoreObservation[], fallback: DynamicFacetValue[]): DynamicFacetValue[] { return pingCastleRiskCategoryCatalog.map(([property, label]) => { const observation = observations.find(item => item.property === property); const sourceValue = observation?.normalizedValue ?? observation?.originalValue; const numericValue = typeof sourceValue === 'number' ? sourceValue : Number(sourceValue); const fallbackValue = fallback.find(value => value.label.toLocaleLowerCase('en-US') === label.toLocaleLowerCase('en-US')); return { value: fallbackValue?.value ?? label.toLocaleLowerCase('en-US'), label, count: Number.isFinite(numericValue) ? numericValue : fallbackValue?.count ?? 0 }; }); } const fullRiskProfileId = 'full-risk-profile'; function RiskProfile({ sourceFacet, categoryFacet, severityFacet, records, profile, reviews, environmentSnapshot, onSelectTool, onSelectCategory, onSelectSeverity }: RiskProfileProps) { const tools = useMemo(() => dashboardRiskTools(sourceFacet?.values ?? []), [sourceFacet]); const [selectedToolId, setSelectedToolId] = useState(''); const selectedTool = tools.find(tool => tool.id === selectedToolId) ?? tools.find(tool => tool.id.includes('pingcastle')) ?? tools[0]; const fullProfile = selectedToolId === fullRiskProfileId; const effectiveProfileId = fullProfile ? fullRiskProfileId : selectedTool?.id ?? fullRiskProfileId; const scopedRecords = useMemo(() => fullProfile ? records : selectedTool && sourceFacet ? records.filter(record => matchesDynamicFacets(record, [sourceFacet], { [sourceFacet.id]: { values: selectedTool.sourceValues } }, reviews)) : [], [fullProfile, records, reviews, selectedTool, sourceFacet]); const scopedFacets = useMemo(() => recountFacetsAgainstActiveFilters({ facets: [categoryFacet, severityFacet].filter((facet): facet is DynamicFacet => Boolean(facet)), records: scopedRecords, filters: {}, reviews, statusFilter: 'all', matchesStatus: (record, status) => profile.matchesStatus(record, status) }), [categoryFacet, profile, reviews, scopedRecords, severityFacet]); const scopedCategories = scopedFacets.find(facet => facet.id === categoryFacet?.id)?.values ?? []; const categories = fullProfile ? tools.map(tool => ({ value: tool.id, label: tool.label, count: tool.count })) : selectedTool?.id.includes('pingcastle') ? dashboardPingCastleRiskCategories( environmentSnapshot?.observations.filter(observation => observation.tool.id === 'pingcastle') ?? [], scopedCategories ) : [...scopedCategories].sort((left, right) => right.count - left.count || left.label.localeCompare(right.label)); const severities = scopedFacets.find(facet => facet.id === severityFacet?.id)?.values ?? []; const deviations = scopedRecords.filter(record => profile.matchesStatus(record, 'deviation')).length; const marked = scopedRecords.filter(record => reviews[record.id]?.include).length; const severityTotal = severities.reduce((sum, value) => sum + value.count, 0); let severityOffset = 0; return (
{tools.length > 0 ? :

Risk profile

}
Marked for report{marked.toLocaleString('en-US')} / {deviations.toLocaleString('en-US')}
{categories.length > 0 ? (
    {categories.slice(0, 4).map(category => (
  • ))}
) :

No risk categories available.

}
{severities.length > 0 ? <>
{severityTotal.toLocaleString('en-US')}100%
    {severities.map(severity => (