module Web.View.FederatedGovernance.Dashboard where
import Web.Types
import Generated.Types
import IHP.Prelude
import IHP.ViewPrelude
import qualified Data.List as List
data FederatedGovernanceDashboardView = FederatedGovernanceDashboardView
{ hubs :: ![Hub]
, widgets :: ![Widget]
, ownerships :: ![WidgetOwnership]
, rules :: ![HubRoutingRule]
, routedCandidates :: ![RequirementCandidate]
, overlays :: ![FederatedPolicyOverlay]
, allDecisions :: ![DecisionRecord]
, allPolicies :: ![PolicyReference]
, stewards :: ![StewardshipRole]
, recentArchives :: ![ArchiveRecord]
}
instance View FederatedGovernanceDashboardView where
html FederatedGovernanceDashboardView { .. } = [hsx|
{panel1Ownership}
{panel2Routing}
{panel3PolicyCompliance}
{panel4Stewardship}
{panel5Archive}
|]
where
-- ── Panel 1: Ownership coverage ──────────────────────────────────
totalWidgets = length widgets
ownedWidgetIds = List.nub (map (.widgetId) ownerships)
ownedCount = length ownedWidgetIds
localCount = length (filter (\o -> o.ownershipType == "local") ownerships)
delegatedCount = length (filter (\o -> o.ownershipType == "delegated") ownerships)
globalCount = length (filter (\o -> o.ownershipType == "global") ownerships)
ownershipPct :: Int
ownershipPct = if totalWidgets == 0 then 0 else (ownedCount * 100) `div` totalWidgets
panel1Ownership = [hsx|
{show ownedCount}
of {show totalWidgets} widgets owned
{show ownershipPct}%
coverage
local: {show localCount}
delegated: {show delegatedCount}
global: {show globalCount}
|]
-- ── Panel 2: Routing activity ─────────────────────────────────────
activeRulesCount = length rules
routedCount = length routedCandidates
hubName hid = maybe (show hid) (.name) (find (\h -> h.id == hid) hubs)
panel2Routing = [hsx|
{show activeRulesCount}
active rules
{show routedCount}
routed (30 days)
{if null rules
then [hsx|
No active routing rules.
|]
else [hsx|
{forEach (take 5 rules) renderRuleRow}
|]}
|]
renderRuleRow :: HubRoutingRule -> Html
renderRuleRow r = [hsx|
{hubName r.sourceHubId}
→
{hubName r.targetHubId}
{maybe mempty (\c -> [hsx|({c})|]) r.matchCategory}
|]
-- ── Panel 3: Policy compliance ────────────────────────────────────
activeOverlaysCount = length overlays
decisionIdsWithPolicy = List.nub $ map (.requirementId) allPolicies
coveredDecisions = length $ filter (\d -> Just d.id `elem` decisionIdsWithPolicy) allDecisions
totalDecisions = length allDecisions
policyPct :: Int
policyPct = if totalDecisions == 0 then 0
else (coveredDecisions * 100) `div` totalDecisions
panel3PolicyCompliance = [hsx|
{show activeOverlaysCount}
active overlays
{show policyPct}%
decision coverage
{if null overlays
then [hsx|
No active policy overlays.
|]
else [hsx|
{forEach overlays \o -> [hsx|
{o.title}
|]}
|]}
|]
-- ── Panel 4: Stewardship coverage ─────────────────────────────────
hubsWithStewards = List.nub (map (.hubId) stewards)
stewardedCount = length hubsWithStewards
totalHubs = length hubs
hubsWithNoSteward = filter (\h -> h.id `notElem` hubsWithStewards) hubs
panel4Stewardship = [hsx|
{show stewardedCount}
of {show totalHubs} hubs stewarded
{show (length hubsWithNoSteward)}
hubs unassigned
{if null hubsWithNoSteward
then [hsx|
All hubs have active stewards.
|]
else [hsx|
Hubs without stewards:
{forEach hubsWithNoSteward \h -> [hsx|
{h.name}
|]}
|]}
|]
-- ── Panel 5: Archive activity ─────────────────────────────────────
archiveByType = List.sortBy (\a b -> compare (fst a) (fst b))
$ map (\grp -> (fst (head grp), length grp))
$ List.groupBy (\a b -> a.subjectType == b.subjectType)
$ List.sortBy (\a b -> compare a.subjectType b.subjectType) recentArchives
panel5Archive = [hsx|
{if null recentArchives
then [hsx|
No artifacts archived in the last 90 days.
|]
else [hsx|
{show (length recentArchives)}
total archived artifacts
{forEach archiveByType \(typ, cnt) -> [hsx|
{typ}: {show cnt}
|]}
|]}
|]