Files
inter-hub/Web/View/ApiConsumers/Index.hs
Bernd Worsch f1978c3888 fix(WP-0014): pre-flight compilation fixes, Tailwind pipeline, and admin seed
A2 — Compilation fixes:
- Remove inline FK constraints from Schema.sql; IHP schema compiler cannot
  parse them. Add 1744329600-restore-fk-constraints.sql migration to restore
  referential integrity at the DB level.
- Rename `#label` → `#label_` throughout to avoid clash with Haskell built-in.
- Fix `hub.id == hid` UUID comparisons to use `toUUID hub.id`.
- Replace non-existent `setStatus`/`respondJson` calls with
  `renderJsonWithStatusCode` throughout Api controllers.
- Fix qualified package import for `cryptohash-sha256` in Auth.hs.
- Add `CanSelect (Text, Text)` instance in Helper.View.
- Refactor HSX inline lambdas to named helper functions in 100+ views
  (GHC cannot infer types for anonymous functions inside quasi-quoted HSX).
- Fix missing imports (IHP.QueryBuilder, IHP.Fetch, Web.Routes, Only, etc.)
  across helpers and controllers.
- Remove duplicate `diffUTCTime` definition in BottleneckDetector.
- Change `createEventForHub` return type from `IO ResponseReceived` to `IO ()`.
- Seed type-registry vocabulary via 1744502400-seed-type-registries.sql
  (moved from Schema.sql where IHP does not execute INSERT statements).

A3 — Tailwind build pipeline:
- Add `tailwindcss` to flake.nix native packages.
- Uncomment `tailwind.exec` process in devenv shell config.
- Add tailwind/tailwind.config.js (scans Web/View/**/*.hs).
- Add tailwind/app.css with @tailwind directives.

A4 — Admin user seed:
- Add 1744416000-seed-admin-user.sql: inserts admin@inter-hub.local
  with bcrypt-hashed password admin1234! (cost 10).
- Add .env.example documenting all required environment variables
  and default admin credentials.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 09:55:12 +00:00

73 lines
3.6 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module Web.View.ApiConsumers.Index where
import Web.Types
import Generated.Types
import IHP.Prelude
import IHP.ViewPrelude
import Web.Routes ()
data IndexView = IndexView { consumers :: ![ApiConsumer] }
instance View IndexView where
html IndexView { .. } = [hsx|
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-semibold">API Consumers</h1>
<p class="text-sm text-gray-500 mt-1">External systems authenticated against /api/v2/</p>
</div>
<a href={NewApiConsumerAction}
class="bg-indigo-600 text-white text-sm font-medium px-4 py-2 rounded hover:bg-indigo-700">
New Consumer
</a>
</div>
<div class="mb-4 flex gap-3 text-sm">
<a href={ApiV2OpenApiJsonAction} target="_blank" class="text-indigo-600 hover:underline">openapi.json</a>
<a href={ApiV2DocsAction} target="_blank" class="text-indigo-600 hover:underline">API Docs</a>
<a href={ApiV2SdkIndexAction} class="text-indigo-600 hover:underline">SDKs</a>
<a href={ShowApiDashboardAction} class="text-indigo-600 hover:underline">Dashboard</a>
</div>
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b border-gray-200">
<tr>
<th class="text-left px-4 py-3 font-medium text-gray-600">Name</th>
<th class="text-left px-4 py-3 font-medium text-gray-600">Manifest</th>
<th class="text-left px-4 py-3 font-medium text-gray-600">Rate Limit</th>
<th class="text-left px-4 py-3 font-medium text-gray-600">Quota/day</th>
<th class="text-left px-4 py-3 font-medium text-gray-600">Status</th>
<th class="px-4 py-3"></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{forEach consumers renderRow}
</tbody>
</table>
</div>
|]
where
renderRow consumer = [hsx|
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 font-medium">
<a href={ShowApiConsumerAction consumer.id} class="text-indigo-600 hover:underline">
{consumer.name}
</a>
</td>
<td class="px-4 py-3 text-gray-500">
{if isJust consumer.hubCapabilityManifestId then "" else "" :: Text}
</td>
<td class="px-4 py-3 text-gray-600">{show consumer.rateLimitPerMinute}/min</td>
<td class="px-4 py-3 text-gray-600">{show consumer.quotaPerDay}</td>
<td class="px-4 py-3">
{renderConsumerStatus consumer.isActive}
</td>
<td class="px-4 py-3 text-right">
<a href={EditApiConsumerAction consumer.id} class="text-gray-400 hover:text-gray-700 text-sm mr-3">Edit</a>
<a href={ApiKeysAction consumer.id} class="text-gray-400 hover:text-gray-700 text-sm">Keys</a>
</td>
</tr>
|]
renderConsumerStatus :: Bool -> Html
renderConsumerStatus True = [hsx|<span class="bg-green-100 text-green-700 text-xs px-2 py-0.5 rounded-full">active</span>|]
renderConsumerStatus False = [hsx|<span class="bg-gray-100 text-gray-500 text-xs px-2 py-0.5 rounded-full">inactive</span>|]