inter-hub-haskell/Web/View/HubRegistry/Show.hs

180 lines
7.5 KiB
Haskell
Raw Normal View History

module Web.View.HubRegistry.Show where
import Web.Types
import Web.Controller.HubRegistry (GaafStatus(..), gaafStatus)
import Generated.Types
import IHP.Prelude
import IHP.ViewPrelude
import Data.Aeson (Value(..), encode)
import qualified Data.Vector as V
import qualified Data.ByteString.Lazy.Char8 as BL
-- | Row from the adopted patterns query.
-- (patternId, patternName, widgetType, patternHubId, adoptionId, isVersionPinned, adoptedAt)
type AdoptedPatternRow = (Id WidgetPattern, Text, Text, Id Hub, Id PatternAdoption, Bool, UTCTime)
data ShowView = ShowView
{ hub :: !Hub
, mManifest :: !(Maybe HubCapabilityManifest)
, healthHistory :: ![HubHealthSnapshot]
, adoptedPatterns :: ![AdoptedPatternRow]
}
instance View ShowView where
html ShowView { .. } =
let gs = gaafStatus mManifest
in [hsx|
<div class="mb-4">
<a href={HubRegistryAction} class="text-sm text-gray-500 hover:text-gray-700">
Hub Registry
</a>
</div>
<div class="flex items-center gap-3 mb-6">
<h1 class="text-2xl font-semibold">{hub.name}</h1>
<span class="text-sm text-gray-400 font-mono">{hub.hubKind}</span>
{gaafBadge gs}
</div>
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="bg-white rounded-lg border border-gray-200 p-4">
<p class="text-xs text-gray-500 uppercase tracking-wide">Domain</p>
<p class="font-medium mt-1">{hub.domain}</p>
</div>
<div class="bg-white rounded-lg border border-gray-200 p-4">
<p class="text-xs text-gray-500 uppercase tracking-wide">Capability Manifest</p>
{manifestCell mManifest hub.id}
</div>
</div>
{case mManifest of
Nothing -> [hsx|
<div class="bg-amber-50 border border-amber-200 rounded p-3 mb-6 text-sm text-amber-800">
No active manifest. <a href={NewHubCapabilityManifestAction} class="underline">Create one</a> to register hub-owned types.
</div>
|]
Just m -> [hsx|
<div class="grid grid-cols-2 gap-4 mb-6">
{jsonArraySection "Widget Types" m.declaredWidgetTypes}
{jsonArraySection "Event Types" m.declaredEventTypes}
{jsonArraySection "Annotation Categories" m.declaredAnnotationCategories}
{jsonArraySection "Policy Scopes" m.declaredPolicyScopes}
</div>
|]}
<h2 class="text-lg font-semibold mb-3">Health History</h2>
{if null healthHistory
then [hsx|<p class="text-sm text-gray-400 mb-6">No snapshots recorded yet.</p>|]
else [hsx|
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="text-xs text-gray-500 border-b border-gray-200">
<th class="text-left py-2">Score</th>
<th class="text-left py-2">Open Candidates</th>
<th class="text-left py-2">Regressed Widgets</th>
<th class="text-left py-2">Stale Decisions</th>
<th class="text-left py-2">Active Bottlenecks</th>
<th class="text-left py-2">Computed At</th>
</tr>
</thead>
<tbody>
{forEach healthHistory renderSnapshotRow}
</tbody>
</table>
</div>
|]}
<h2 class="text-lg font-semibold mb-3">Adopted Patterns</h2>
{if null adoptedPatterns
then [hsx|<p class="text-sm text-gray-400">No patterns adopted yet. <a href={WidgetPatternsAction} class="text-indigo-600 hover:underline">Browse patterns </a></p>|]
else [hsx|
<div class="space-y-2">
{forEach adoptedPatterns renderAdoptedPattern}
</div>
|]}
|]
manifestCell :: Maybe HubCapabilityManifest -> Id Hub -> Html
manifestCell Nothing hubId = [hsx|
<div class="mt-1">
<span class="text-sm text-gray-400">None</span>
<a href={NewHubCapabilityManifestAction}
class="ml-2 text-xs text-indigo-600 hover:underline">Create</a>
</div>
|]
manifestCell (Just m) _ = [hsx|
<div class="mt-1 flex items-center gap-2">
<span class="font-mono text-sm">{m.manifestVersion}</span>
<a href={ShowHubCapabilityManifestAction { hubCapabilityManifestId = m.id }}
class="text-xs text-indigo-600 hover:underline">View</a>
</div>
|]
gaafBadge :: GaafStatus -> Html
gaafBadge GaafCompliant =
[hsx|<span class="px-2 py-0.5 rounded text-xs bg-green-100 text-green-800">GAAF compliant</span>|]
gaafBadge GaafDraftOnly =
[hsx|<span class="px-2 py-0.5 rounded text-xs bg-amber-100 text-amber-800">draft manifest</span>|]
gaafBadge GaafNoManifest =
[hsx|<span class="px-2 py-0.5 rounded text-xs bg-red-100 text-red-700">no manifest</span>|]
jsonArraySection :: Text -> Value -> Html
jsonArraySection title val = [hsx|
<div class="bg-white rounded-lg border border-gray-200 p-4">
<h3 class="text-sm font-semibold text-gray-700 mb-2">
{title} <span class="text-gray-400 font-normal ml-1">({arrayLen val})</span>
</h3>
{renderArrayItems val}
</div>
|]
renderArrayItems :: Value -> Html
renderArrayItems (Array v) | V.null v =
[hsx|<p class="text-xs text-gray-400">None declared</p>|]
renderArrayItems (Array v) = [hsx|
<ul class="space-y-1">
{forEach (V.toList v) renderItem}
</ul>
|]
renderArrayItems _ = [hsx|<p class="text-xs text-gray-400"></p>|]
renderItem :: Value -> Html
renderItem (String t) = [hsx|<li class="font-mono text-xs text-gray-700">{t}</li>|]
renderItem v = [hsx|<li class="font-mono text-xs text-gray-500">{cs (BL.unpack (encode v)) :: Text}</li>|]
arrayLen :: Value -> Text
arrayLen (Array v) = tshow (V.length v)
arrayLen _ = "0"
renderSnapshotRow :: HubHealthSnapshot -> Html
renderSnapshotRow s = [hsx|
<tr class="border-b border-gray-100 text-sm">
<td class="py-2 font-medium">{tshow s.healthScore}</td>
<td class="py-2">{tshow s.openCandidates}</td>
<td class="py-2">{tshow s.regressedWidgets}</td>
<td class="py-2">{tshow s.staleDecisions}</td>
<td class="py-2">{tshow s.activeBottlenecks}</td>
<td class="py-2 text-gray-500">{tshow s.computedAt}</td>
</tr>
|]
renderAdoptedPattern :: AdoptedPatternRow -> Html
renderAdoptedPattern (patternId, patternName, widgetType, _, _, isPinned, adoptedAt) = [hsx|
<div class="bg-white rounded border border-gray-200 p-3 flex items-center justify-between">
<div>
<a href={ShowWidgetPatternAction { widgetPatternId = patternId }}
class="font-medium text-sm text-indigo-700 hover:underline">
{patternName}
</a>
<span class="ml-2 font-mono text-xs text-gray-400">{widgetType}</span>
</div>
<div class="flex items-center gap-2 text-xs text-gray-500">
{if isPinned
then [hsx|<span class="px-2 py-0.5 rounded bg-blue-100 text-blue-700">pinned</span>|]
else [hsx|<span class="px-2 py-0.5 rounded bg-gray-100 text-gray-500">follow latest</span>|]}
<span>{tshow adoptedAt}</span>
</div>
</div>
|]