Home.razor 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. @page "/"
  2. @using RackPeek.Domain.Graph.Serialisers
  3. @using RackPeek.Domain.Graph.UseCases
  4. @using RackPeek.Domain.Resources
  5. @using RackPeek.Domain.Resources.Hardware
  6. @using RackPeek.Domain.Resources.Services.UseCases
  7. @using RackPeek.Domain.Resources.SystemResources.UseCases
  8. @using Shared.Rcl.Components
  9. @using Shared.Rcl.Components.Graphs
  10. @inject GetSystemSummaryUseCase SystemSummaryUseCase
  11. @inject GetServiceSummaryUseCase ServiceSummaryUseCase
  12. @inject GetHardwareUseCaseSummary HardwareSummaryUseCase
  13. @inject BuildPhysicalTopologyUseCase TopologyUseCase
  14. @inject BuildLogicalGraphUseCase LogicalUseCase
  15. <PageTitle>Home</PageTitle>
  16. <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
  17. @if (_loading)
  18. {
  19. <div class="text-zinc-500">loading summary…</div>
  20. }
  21. else
  22. {
  23. <!--
  24. Uniform-height card grid. Every card is `h-80` regardless of content;
  25. taller content scrolls inside. `grid-flow-dense` packs the wider
  26. diagram cards without leaving holes.
  27. -->
  28. <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 auto-rows-[20rem] grid-flow-dense gap-4">
  29. <!-- Totals -->
  30. <div class="@_cardClass">
  31. <div class="@_cardHeaderClass">Totals</div>
  32. <div class="@_cardBodyClass">
  33. <div class="grid grid-cols-2 gap-y-2 text-sm">
  34. <div class="hover:text-emerald-300">
  35. <NavLink href="hardware/tree">→ Hardware</NavLink>
  36. </div>
  37. <div class="text-right">@_hardware!.TotalHardware</div>
  38. <div class="hover:text-emerald-300">
  39. <NavLink href="systems/list">→ Systems</NavLink>
  40. </div>
  41. <div class="text-right">@_system!.TotalSystems</div>
  42. <div class="hover:text-emerald-300">
  43. <NavLink href="services/list">→ Services</NavLink>
  44. </div>
  45. <div class="text-right">@_service!.TotalServices</div>
  46. </div>
  47. </div>
  48. </div>
  49. <!-- Tools -->
  50. <div class="@_cardClass">
  51. <div class="@_cardHeaderClass">Tools</div>
  52. <div class="@_cardBodyClass">
  53. <ul class="space-y-2 text-sm">
  54. <li><NavLink href="visualise" class="block hover:text-emerald-300" data-testid="home-tool-visualise">→ Visualise</NavLink></li>
  55. <li><NavLink href="subnets" class="block hover:text-emerald-300" data-testid="home-tool-subnets">→ Subnet Browser</NavLink></li>
  56. <li><NavLink href="cli" class="block hover:text-emerald-300" data-testid="home-tool-cli">→ CLI Emulator</NavLink></li>
  57. <li><NavLink href="yaml" class="block hover:text-emerald-300" data-testid="home-tool-yaml">→ YAML Editor</NavLink></li>
  58. <li><NavLink href="ansible/inventory" class="block hover:text-emerald-300" data-testid="home-tool-ansible">→ Ansible Inventory Generator</NavLink></li>
  59. <li><NavLink href="ssh/export" class="block hover:text-emerald-300" data-testid="home-tool-ssh">→ SSH Config Export</NavLink></li>
  60. <li><NavLink href="hosts/export" class="block hover:text-emerald-300" data-testid="home-tool-hosts">→ Hosts File Export</NavLink></li>
  61. <li><NavLink href="docs" class="block hover:text-emerald-300" data-testid="home-tool-docs">→ Documentation</NavLink></li>
  62. </ul>
  63. </div>
  64. </div>
  65. <!-- Physical / Hardware topology -->
  66. <div class="@_cardClass sm:col-span-2 sm:row-span-2">
  67. <div class="flex items-center justify-between mb-3 flex-shrink-0">
  68. <div class="text-xs text-zinc-500 uppercase tracking-wider">Physical Topology</div>
  69. <NavLink href="visualise/topology"
  70. class="text-xs text-zinc-400 hover:text-emerald-400"
  71. data-testid="home-topology-open">
  72. open →
  73. </NavLink>
  74. </div>
  75. <div class="flex-1 min-h-0 overflow-hidden">
  76. <GraphView Source="@_topologySource" TestId="home-topology-preview"/>
  77. </div>
  78. </div>
  79. <!-- Logical / Services & Systems -->
  80. <div class="@_cardClass sm:col-span-2 sm:row-span-2">
  81. <div class="flex items-center justify-between mb-3 flex-shrink-0">
  82. <div class="text-xs text-zinc-500 uppercase tracking-wider">Logical View</div>
  83. <NavLink href="visualise/logical"
  84. class="text-xs text-zinc-400 hover:text-emerald-400"
  85. data-testid="home-logical-open">
  86. open →
  87. </NavLink>
  88. </div>
  89. <div class="flex-1 min-h-0 overflow-hidden">
  90. <GraphView Source="@_logicalSource" TestId="home-logical-preview"/>
  91. </div>
  92. </div>
  93. <!-- Tags (component renders its own internal header) -->
  94. <div class="@_cardClass">
  95. <div class="@_cardBodyClass">
  96. <TagListComponent/>
  97. </div>
  98. </div>
  99. <!-- Labels -->
  100. <div class="@_cardClass">
  101. <div class="@_cardBodyClass">
  102. <LabelListComponent/>
  103. </div>
  104. </div>
  105. <!-- Hardware breakdown -->
  106. <div class="@_cardClass">
  107. <div class="@_cardHeaderClass">Hardware</div>
  108. <div class="@_cardBodyClass">
  109. <ul class="space-y-2 text-sm">
  110. <li class="text-zinc-100">├─ Hardware (@_hardware!.TotalHardware)</li>
  111. @if (_hardware.HardwareByKind.Any())
  112. {
  113. <ul class="ml-4 mt-2 border-l border-zinc-800 pl-4 space-y-1">
  114. @foreach (var (kind, count) in _hardware.HardwareByKind.OrderByDescending(x => x.Value))
  115. {
  116. var pluralKind = Resource.KindToPlural(kind);
  117. <NavLink href="@($"/{Uri.EscapeDataString(pluralKind)}/list")" class="block">
  118. <li class="text-zinc-500 hover:text-emerald-300">
  119. └─ @pluralKind (@count)
  120. </li>
  121. </NavLink>
  122. }
  123. </ul>
  124. }
  125. </ul>
  126. </div>
  127. </div>
  128. <!-- Systems breakdown -->
  129. <div class="@_cardClass">
  130. <div class="@_cardHeaderClass">Systems</div>
  131. <div class="@_cardBodyClass">
  132. <ul class="space-y-3 text-sm">
  133. <li>
  134. <div class="text-zinc-100">├─ Total (@_system!.TotalSystems)</div>
  135. @if (_system.SystemsByType.Any())
  136. {
  137. <ul class="ml-4 mt-2 border-l border-zinc-800 pl-4 space-y-1">
  138. <li class="text-zinc-400">Types</li>
  139. @foreach (var (type, count) in _system.SystemsByType.OrderByDescending(x => x.Value))
  140. {
  141. <li class="text-zinc-500 hover:text-emerald-300">
  142. <NavLink href="@($"systems/list?type={Uri.EscapeDataString(type)}")" class="block">
  143. └─ @type (@count)
  144. </NavLink>
  145. </li>
  146. }
  147. </ul>
  148. }
  149. @if (_system.SystemsByOs.Any())
  150. {
  151. <ul class="ml-4 mt-2 border-l border-zinc-800 pl-4 space-y-1">
  152. <li class="text-zinc-400">Operating Systems</li>
  153. @foreach (var (os, count) in _system.SystemsByOs.OrderByDescending(x => x.Value))
  154. {
  155. <li class="text-zinc-500 hover:text-emerald-300">
  156. <NavLink href="@($"systems/list?os={Uri.EscapeDataString(os)}")" class="block">
  157. └─ @os (@count)
  158. </NavLink>
  159. </li>
  160. }
  161. </ul>
  162. }
  163. </li>
  164. </ul>
  165. </div>
  166. </div>
  167. <!-- Services breakdown -->
  168. <div class="@_cardClass">
  169. <div class="@_cardHeaderClass">Services</div>
  170. <div class="@_cardBodyClass">
  171. <ul class="text-sm">
  172. <li class="text-zinc-100">├─ Total (@_service!.TotalServices)</li>
  173. <li class="ml-4 text-zinc-500">└─ IP Addresses (@_service!.TotalIpAddresses)</li>
  174. </ul>
  175. </div>
  176. </div>
  177. </div>
  178. }
  179. </div>
  180. @code {
  181. private bool _loading = true;
  182. private SystemSummary? _system;
  183. private AllServicesSummary? _service;
  184. private HardwareSummary? _hardware;
  185. private string? _topologySource;
  186. private string? _logicalSource;
  187. private static readonly MermaidSerialiser _serialiser = new();
  188. // Shared card chrome — defined once so every card looks identical.
  189. // Card height is set by the parent grid's `auto-rows-[20rem]`; cards
  190. // that span multiple rows (e.g. diagram cards with `row-span-2`) get
  191. // the combined height plus gap automatically.
  192. private const string _cardClass =
  193. "border border-zinc-800 rounded-md p-4 bg-zinc-900/30 flex flex-col overflow-hidden";
  194. private const string _cardHeaderClass =
  195. "text-xs text-zinc-500 uppercase tracking-wider mb-3 flex-shrink-0";
  196. private const string _cardBodyClass =
  197. "flex-1 min-h-0 overflow-y-auto pr-1";
  198. protected override async Task OnInitializedAsync()
  199. {
  200. Task<SystemSummary> systemTask = SystemSummaryUseCase.ExecuteAsync();
  201. Task<AllServicesSummary> serviceTask = ServiceSummaryUseCase.ExecuteAsync();
  202. Task<HardwareSummary> hardwareTask = HardwareSummaryUseCase.ExecuteAsync();
  203. Task<RackPeek.Domain.Graph.Graph> topologyTask = TopologyUseCase.ExecuteAsync();
  204. Task<RackPeek.Domain.Graph.Graph> logicalTask = LogicalUseCase.ExecuteAsync();
  205. await Task.WhenAll(systemTask, serviceTask, hardwareTask, topologyTask, logicalTask);
  206. _system = systemTask.Result;
  207. _service = serviceTask.Result;
  208. _hardware = hardwareTask.Result;
  209. _topologySource = _serialiser.Serialise(topologyTask.Result);
  210. _logicalSource = _serialiser.Serialise(logicalTask.Result);
  211. _loading = false;
  212. }
  213. }