module Web.View.FederatedGovernance.Dashboard where
import Web.Types
import Generated.Types
import IHP.Prelude
import IHP.ViewPrelude
import Web.Routes ()
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 -> toUUID h.id == hid) hubs)
panel2Routing = [hsx|
{show activeRulesCount}
active rules
{show routedCount}
routed (30 days)
{renderRulesSection rules}
|]
renderRulesSection :: [HubRoutingRule] -> Html
renderRulesSection [] = [hsx|No active routing rules.
|]
renderRulesSection rs = [hsx|
{forEach (take 5 rs) renderRuleRow}
|]
renderRuleRow :: HubRoutingRule -> Html
renderRuleRow r = [hsx|
{hubName r.sourceHubId}
→
{hubName r.targetHubId}
{maybe mempty renderMatchCategory r.matchCategory}
|]
renderMatchCategory :: Text -> Html
renderMatchCategory c = [hsx|({c})|]
-- ── Panel 3: Policy compliance ────────────────────────────────────
activeOverlaysCount = length overlays
decisionIdsWithPolicy = List.nub $ map (Just . (.decisionId)) 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
{renderOverlaysList overlays}
|]
-- ── Panel 4: Stewardship coverage ─────────────────────────────────
hubsWithStewards = List.nub (map (.hubId) stewards)
stewardedCount = length hubsWithStewards
totalHubs = length hubs
hubsWithNoSteward = filter (\h -> toUUID h.id `notElem` hubsWithStewards) hubs
panel4Stewardship = [hsx|
{show stewardedCount}
of {show totalHubs} hubs stewarded
{show (length hubsWithNoSteward)}
hubs unassigned
{renderUnstewarded hubsWithNoSteward}
|]
-- ── Panel 5: Archive activity ─────────────────────────────────────
archiveByType = List.sortBy (\a b -> compare (fst a) (fst b))
$ map (\grp -> ((head grp).subjectType, length grp))
$ List.groupBy (\a b -> a.subjectType == b.subjectType)
$ List.sortBy (\a b -> compare a.subjectType b.subjectType) recentArchives
panel5Archive = [hsx|
{renderArchiveActivity recentArchives archiveByType}
|]
renderOverlaysList :: [FederatedPolicyOverlay] -> Html
renderOverlaysList [] = [hsx|No active policy overlays.
|]
renderOverlaysList overlays = [hsx|
{forEach overlays renderOverlayTitle}
|]
renderOverlayTitle :: FederatedPolicyOverlay -> Html
renderOverlayTitle o = [hsx|{o.title}
|]
renderUnstewarded :: [Hub] -> Html
renderUnstewarded [] = [hsx|All hubs have active stewards.
|]
renderUnstewarded hs = [hsx|
Hubs without stewards:
{forEach hs renderUnstewardedHub}
|]
renderUnstewardedHub :: Hub -> Html
renderUnstewardedHub h = [hsx|{h.name}|]
renderArchiveActivity :: [ArchiveRecord] -> [(Text, Int)] -> Html
renderArchiveActivity [] _ = [hsx|No artifacts archived in the last 90 days.
|]
renderArchiveActivity archives byType = [hsx|
{show (length archives)}
total archived artifacts
{forEach byType renderArchiveTypeChip}
|]
renderArchiveTypeChip :: (Text, Int) -> Html
renderArchiveTypeChip (typ, cnt) = [hsx|
{typ}: {show cnt}
|]