Skip to content

Services

The Services view lists every Wolverine service that has registered with CritterWatch. From here you drill into a single service's detail page, which is the operational hub for one logical Wolverine application — its nodes, its agents, its message handlers, its endpoints, its tenants, its DDL, and the rest.

Services List

The list page renders both as a card grid (default) and as a sortable table. Use the toggle in the toolbar to switch.

Each card / row shows:

FieldMeaning
NameWolverine ServiceName — unique within the cluster
LabelHuman-readable display name; defaults to the service name and is editable on the service's Overview tab
VersionWolverine version reported on the most recent VersionDetected snapshot
StatusAggregate health roll-up — Healthy / Warning / Critical / Unknown
NodesActive process instances heartbeating in the cluster
Msgs/hrRecent throughput across all handlers and endpoints
DLQUnresolved dead letters in this service's stores
AlertsOpen alerts (raised + elevated) attributed to this service

Clicking a service opens its detail page. The detail page binds the active tab to the URL via ?tab=<name> so deep links and back-button navigation always land on the right pane.

Service Detail

The detail page is organised into seven always-on tabs and two conditional tabs. The conditional tabs (Tenants, HTTP) are only rendered when the service surfaces the relevant capability.

Overview · Storage · Durability · Cluster · Messaging · Metrics · Endpoints · [Tenants] · [HTTP]

URL backward compatibility

Two reorganisations changed tab names; both keep their old URLs working so saved bookmarks don't 404:

Old ?tab=Lands on
?tab=messagesMessaging tab (renamed in #112)
?tab=nodesCluster tab, Nodes view
?tab=agentsCluster tab, Agents view

If a service has any critical alerts and no explicit ?tab= is set, the detail page opens on the Alerts tab so the operator sees the active incident first.


Overview Tab

The Overview pane is the at-a-glance identity card for the service. It is the only tab that always opens, and it is the home for editable service-level configuration.

Mini metrics dashboard

Five compact cards across the top:

CardSource
Msgs/hrTotal messages handled per hour (sent + received)
Avg Exec TimeAverage handler execution time, milliseconds (excludes middleware)
Avg Effective TimeAverage end-to-end processing time including middleware, serialization, and transport
DLQ/hrMessages moved to the dead-letter queue per hour
Exceptions/hrHandler exceptions per hour (includes retried + dead-lettered)

Identity

A two-column descriptions table with:

  • Service ID — Wolverine ServiceName

  • Label — editable inline via the pencil-affordance EditableLabelCell (#101). Click the label, type a new value, press Return or click away to commit. The change emits RenameServiceLabel and is reflected immediately across every page that shows this service.

  • Version — Wolverine version

  • Communication URI — how CritterWatch talks back to the service (typically wolverine.signalr:// for the BFF transport)

  • TenancyNone, Single, ConjoinedTenancy, DefaultTenant, or ExtendedDynamic. The Tenants tab is shown for any tenancy mode beyond Single / None; the per-row actions (Add / Disable / Enable / Remove / Hard delete) are only enabled when at least one event-store is DynamicMultiple (#103 Pass 2a).

  • Tenant ID Style (#103 Pass 2c) — surfaces Marten's StoreOptions.TenantIdStyle when the service has any tenancy. One of:

    • CaseSensitive — tenant IDs stored exactly as supplied; case must match at session-open time.
    • ForceLowerCase — tenant IDs silently lowercased on every reference.
    • ForceUpperCase — tenant IDs silently uppercased on every reference.

    When multiple document stores disagree the tag shows the first value with a (mixed) suffix and a warning tooltip. Hidden when the service has no tenancy.

Health checks

A card on the Overview tab confirms whether this service is wired up to ASP.NET Core health checks (#73). Two states:

  • Registered (green) — the service is exposing its Wolverine state to anything that asks via the standard /healthz endpoint, with the registered check names and tags listed as chips. This is what Kubernetes liveness and readiness probes consume.
  • Not registered (amber) — the service has no Wolverine-aware health check registered. Without one, k8s and load balancers can't tell whether the runtime is up or jammed.

The card is hidden entirely when the monitored service is running an older Wolverine.CritterWatch build that doesn't ship the registration descriptor.

For the why and how, see Health Checks. The "Operator docs" link in the card goes there directly.

Trace provider binding

A card on the Overview tab lets the operator pick which configured OTel store CritterWatch queries when looking up traces for this service's saga instances and handler runs. The dropdown lists every provider configured globally (Settings → Trace providers); leaving it empty falls back to the default. The "Manage providers" link opens Settings.


Storage Tab

Wave 2 (#112) consolidated everything storage-shaped into one tab so operators investigating a "where is this document / event / message persisted?" question stop scrolling past four stacked sections at the bottom of Overview.

Message Stores

Per-store row with live persistence counts:

ColumnMeaning
IconPostgres / SqlServer / RabbitMQ / etc.
URIDatabase / store URI
RoleMain (durability tables) or Ancillary (DLQ-only or per-tenant overflow)
IncomingPersisted incoming envelopes (inbox)
OutgoingPersisted outgoing envelopes (outbox)
ScheduledPersisted scheduled-send envelopes awaiting their delivery time
HandledSuccessfully handled envelopes still retained for idempotency (KeepAfterMessageHandling)
Dead LetterDLQ count for this store. Click a non-zero count to drill into the Dead Letters page pre-filtered to this store + service.

Wolverine Configuration snapshot

Reference data captured on each VersionDetected (i.e. once per deploy). Three sub-tables:

  • Service Settings — top-level WolverineOptions (default timeouts, listener kill switch, metrics policy, remote-invocation behaviour)
  • Durability — Operational — operational identity controls
  • Durability — Polling & Cadence — reassign / health / scheduled-job intervals
  • Durability — Retention — handled-message + DLQ + node-history retention windows

Each field has a tooltip pulled from XML doc comments via the auto-generated tooltip lookup; field descriptions stay in sync with the upstream Wolverine sources.

Document Stores & Event Stores

Per-store cards listing document mappings, registered projections, and DDL. The first Document Store (or first Event Store, if no document stores are present) is expanded by default — biased toward the most recently shipped surface. See DdlBlock for the syntax-highlighted DDL panel.

DbContexts

The third storage subsection — one card per EF Core DbContext registered in the service. Picked up automatically: contexts wired through AddDbContextWithWolverineIntegration get the full Wolverine-integrated picture, and any plain AddDbContext<>() registration is detected by the implicit-discovery hook in UseEntityFrameworkCoreTransactions, so untracked contexts still show up — just with the integration badges off.

Each card's title row carries a strip of badges. Read them in order:

  • Wolverine — green when the model has been mapped through MapWolverineEnvelopeStorage. This is the single most important badge to triage missing-publish issues. When green, your domain events publish atomically with SaveChangesAsync because the envelope tables share the context's connection. When grey ("Plain"), Wolverine isn't driving this context's transaction at all — any messaging happens on a separate connection, which is correct but won't give you transactional outbox guarantees.
  • TenancySingle for a single-database context (info color), ConnectionString or DbDataSource for multi-tenant (warning color). Multi-tenant cards expand to show the per-tenant database list (server + database + tenantId only — never the raw connection string).
  • TxEager (green) when transactional middleware opens an explicit database transaction up front; Lightweight (amber) when atomicity is delegated to SaveChangesAsync. Absent when Wolverine isn't wrapping this handler in transactional middleware.
  • Domain eventsOutgoingDomainEvents (the per-handler OutgoingDomainEvents collection is the source) or PerEntityType (one or more entity-level domain-event scrapers are registered). When PerEntityType, expand the card to see exactly which entity publishes which event type.
  • OutboxMapped (green-path) means the envelope tables share this context's connection. ExternalConnection means Wolverine's outbox factory is registered but the envelope tables live on a different connection — correct, but slower under load. Absent for plain contexts.
  • Wolverine-managed — present when the application is using UseEntityFrameworkCoreWolverineManagedMigrations. Wolverine's resource-startup pipeline will create or migrate this context's database on its own. Without the badge, schema management is your responsibility (dotnet ef database update or equivalent).

Below the badges, the Pending migrations field starts at "—" until you click Check pending migrations. The action is on-demand — clicking it runs IMigrator.GetPendingMigrationsAsync on the target service, opening a connection to the database and reading the __EFMigrationsHistory table. That's a synchronous round-trip per click, which is why we don't bake it into the regular capabilities snapshot. Inline error text appears under the field if the probe couldn't reach the database (transient connection failure, missing permissions, or a provider that doesn't support migrations).

Cards expand to a per-entity table — table name, primary-key columns, index count, foreign-key count, plus chips for sagas, owned types, and view-mapped entities. Saga rows cross-link to the Sagas tab so you can jump straight from "what does this context manage" to "what does this saga handle". Drill into an entity row for its full index and foreign-key detail.

Brokers

Configured messaging brokers (RabbitMQ, Kafka, Azure Service Bus, etc.) with each broker's declared queue / topic surface area.

Broker health columns (#70)

Each broker row carries a small health strip that surfaces the connection's actual condition rather than the configuration alone.

  • Status — a traffic-light dot. Green when the connection is steady, orange when it has recently flapped (lost-and-recovered inside the last few minutes), red when it is currently down. Hover the dot for the probe's last description — typically the close-reason for an unhealthy connection or the recovery timestamp for a degraded one.
  • Last successful — relative time since the most recent good probe (2m ago, 1h ago). Drift on this column is the leading indicator that something started failing in the background; if the row says "Healthy" but Last successful is climbing past the probe cadence (60 s by default), the broker is flapping faster than the probe can resolve it.
  • Reconnects — running counter of times this transport has lost and re-established the broker connection since the host started. A single bump is harmless transient weather; sustained climbing is the signal something is actually broken — typically a misbehaving network path, an undersized broker, or expired credentials retrying. The counter renders bold-amber once it leaves zero.
  • Cert expiry — the TLS certificate's expiry date when TLS is configured. The chip switches to amber within 14 days of expiry; the action is to rotate the cert before the broker starts refusing new connections.

A status of Unknown (grey dot, "—" for the timestamp) means the transport hasn't connected yet. That is normal during cold starts — the first probe pass runs ~60 s after the host comes up. If a row stays Unknown past the warm-up window, the transport is misconfigured or disabled; check the host's startup logs.

The same data is surfaced on the per-broker detail page (click the broker name) with extra room for the probe description and the reporting node number — useful when multiple nodes probe the same broker and you need to spot which one is seeing the issue.


Durability Tab

Live operator surface for the inbox/outbox tables backing this service.

The Durability tab table mirrors the Storage tab's Message Stores table but adds a clickable row — the URI cell drills into a dedicated Per-message-store detail page (#105) at /service/:id/message-store/:storeUri. The detail page is the per-store equivalent of this tab — recent inbox/outbox samples, DLQ counts, scheduled-job queue, and the DDL for that store's durability tables.

Use this tab when:

  • A persistence count looks wrong and you need a per-store drill-in
  • You want to see the actual DDL Wolverine has installed
  • You need the per-store DLQ count without leaving the service page

The row backgrounds shade gold when an ?focus=<storeUri> query parameter is present — used by deep links from the alerts feed when a DLQ-rate alert fires.


Cluster Tab

#106 collapsed the standalone Nodes and Agents tabs into a single Cluster tab with a segmented view-mode toggle. Both views are full operational surfaces — switching used to lose scroll, filter, and selection state, so they now sit side-by-side under one parent.

Cluster view toggle

A radio group at the top of the tab switches between:

  • Nodes — process-instance lens
  • Agents — agent-assignment lens

The active view is URL-bound via ?view=nodes|agents so deep links and operator bookmarks survive remounts.

Nodes view

Cluster-wide actions

ActionEffect
Trigger ElectionForce a new leader election. Disabled while OperationsEnabled is false. Confirmation popconfirm.
Clear Node History (overflow menu)Trim the node-history table; retains the 10 most recent records

The destructive Trigger Election is the primary action; Clear Node History is demoted to an overflow kebab so an operator can't fat-finger it next to the election button.

Node view-mode toggle (#107)

A second toggle inside the Nodes view switches between:

  • Cards — one el-card per node with a Leader pill, alert badge, last health check, version, agents-assigned count, and Eject button
  • List — dense table with the same surface area for big clusters

Heuristic auto-flips to List at 6+ nodes; an explicit operator override is URL-bound via ?nodeView=cards|list for share-and-restore.

Per-node columns / fields

FieldMeaning
Heartbeat dotLiveness pill — green / amber / red / grey. See Heartbeat dot below
Node #Wolverine node number; clickable — drills into the per-node detail page (#108)
RoleLeader pill if this node holds the leadership lease
AlertsPer-node active-alert badge (#106 rollup) — click to focus the alerts list filtered to that node
VersionWolverine version on this node
UptimeTime since started; tooltip shows the wall-clock start time
Last health checkRelative-time short-form; tooltip shows the absolute timestamp
AgentsCount of agents currently assigned to this node
Actions → EjectEjectNode(nodeNumber) — danger popconfirm, redistributes assigned agents

Heartbeat dot

Each running service sends a quick liveness ping to CritterWatch every 30 seconds. The dot beside every node number turns the dot through four states based on the gap since the last ping:

ColourStateMeaningWhat to do
GreenLiveHeartbeat received within the last 60 s.Nothing — the node is healthy.
AmberStaleAt least one heartbeat missed (60–150 s old).Watch for a few more seconds — a single missed beat usually means a slow GC or a brief network blip.
RedSilentFive or more heartbeats missed (older than 150 s, or never received).Open the per-node detail page. The node has likely hung, lost its broker connection, or been killed without graceful shutdown. Check that the service process is up, the broker is reachable, and the network between them isn't partitioned.
GreyUnknownThe node has only just started and no heartbeat has arrived yet, or the service hasn't been integrated with heartbeats.Usually fine — older deployments that haven't been rebuilt against a heartbeat-aware Wolverine still show grey. If the node has been around for several minutes and stays grey, the service may need to be redeployed against the current Wolverine.CritterWatch package.

Hovering the dot reveals the exact Last heartbeat: <timestamp> line plus a one-sentence description of the colour, so operators don't have to remember the rules.

The thresholds are sized against the default 30 s cadence (two missed beats turns the dot amber; five missed beats turns it red). Services that ship a slower or faster cadence — supplied via the heartbeatInterval parameter on AddCritterWatchMonitoring — still use the same wall-clock thresholds today; we'll revisit per-service scaling when a real deployment asks for it.

Agents view

Agent-assignment lens — every registered agent (projection shards, subscriptions, leadership, durability, scheduled-message agents, etc.) with its current node and status:

ColumnMeaning
Agent URIUnique identifier (e.g. wolverine://leader/, marten://projection/TripSummary:All)
StatusHealthy / Degraded / Offline
Assigned NodeWhich node currently owns this agent
Last SeenTime of most recent health report

Per-row actions:

ActionEffect
PinPinAgentToNode — keep this agent on a chosen node across rebalances
UnpinUnpinAgent — drop the assignment override

If a deep link supplies ?scheme=<scheme> (e.g. from the Topology page), the agent table is pre-filtered to that scheme regardless of which view the URL landed on.


Messaging Tab

#112 renamed this tab from "Messages" to "Messaging" — the previous name collided with the global Inbox / Outbox concept and operators kept landing here looking for envelopes. The tab actually catalogs handler / message-type configuration — what messages the service knows how to handle, and where it routes published messages.

Filters

A namespace dropdown at the top trims the table to a single C# namespace. Useful for huge handler graphs.

Columns

ColumnMeaning
Message TypeShort type name; tooltip shows the full namespace; clickable — drills into the per-message-type detail page
Handler or DestinationThree row kinds:
handler — single handler type
separated-handler — handler routed to specific endpoint(s)
destination — published-only, with the target endpoint(s)
Msgs/hrPer-message-type throughput (sent or received depending on row kind)
Avg ExecHandler execution time
Avg EffEffective time including middleware
DLQ/hrDLQ rate for this message type
Exc/hrException rate for this message type
ActionsDLQ drill-in + Replay All / Discard All overflow

The Messaging tab is the standard entry point into the HandlerChainDetailPage — clicking the message type or handler type opens the per-handler page with the full source-code-view, middleware chain, and per-endpoint sticky routing.


Metrics Tab

Per-message-type metrics table, sortable. Same fields as the Messaging tab but without the routing column — pure throughput, exec, effective, DLQ, and exception counters. Useful when you don't care about which handler runs the message and you just want the hot-path list.


Endpoints Tab

The full listener + sender table for this service, with per-row lifecycle controls.

Filters

FilterMeaning
SearchFree text against URI, name, scheme
Endpoint typeScheme dropdown — local, rabbitmq, kafka, sqlserver, etc.
ModeBufferedInMemory / Inline / Durable

A column picker (popover) lets the operator hide / show columns. The row-identifier (Endpoint URI) and the Actions column are locked — they can't be hidden.

Per-endpoint columns

ColumnMeaning
URIEndpoint address (clickable; opens EndpointDetailPage)
ModeListener / Sender / Both
StatusAccepting / Stopped / TooBusy / Latched / Paused / Draining
TransportRabbitMQ / Kafka / InMemory / SqlServer / etc.
Circuit BreakerActive / Paused — failure-rate gating state
Back PressureNone / Triggered

Per-endpoint actions

ActionEffect
PausePauseListener(uri) — hard-stops processing on the endpoint immediately. Existing in-flight work is abandoned.
Drain (#67)DrainListener(uri) — graceful-shutdown. Stops accepting new messages, lets queued + in-flight messages finish, then halts. The right call before a deploy.
RestartRestartListener(uri) — resumes a paused or drained endpoint
Edit Buffering LimitsUpdateEndpointBufferingLimits — adjust MaximumMessagesToReceive and related buffer thresholds. Drawer form.
Edit Circuit BreakerUpdateEndpointCircuitBreaker — adjust failure-rate, sampling window, and pause duration

Batch actions

When one or more endpoint rows are selected, the toolbar exposes Pause Selected and Restart Selected for batch lifecycle control.

The Drain action is intentionally per-row only — draining 30 endpoints at once is almost never the right move; if you really want that, the cluster-wide PauseAllListeners / RestartAllListeners commands exist for the all-or-nothing case (issued from the parent operations menu).

Pause / Drain / Restart — when to use which

ScenarioUse
Hot-fix needs a stop-the-worldPause
Pre-deploy graceful shutdownDrain
Resume after either Pause or DrainRestart
Circuit-broken upstream, want to throttle without stoppingEdit Circuit Breaker

Tenants Tab (conditional)

Visible whenever the service has any tenancy mode beyond Single / None (#103 Pass 2a). The tab renders one of two layouts depending on cardinality:

Dynamic case (DynamicMultiple)

Editable list — operators add / disable / enable / remove / hard-delete tenants at runtime via the relay commands. Top toolbar:

  • Refresh — re-issues RequestTenantList to the service
  • Add Tenant — opens the tenant-creation drawer (AddTenant command)

Per-tenant columns:

ColumnMeaning
Tenant IDTenant identifier as the application sees it
Database URIPer-tenant database connection string
Executions (1h)How many handler executions this tenant generated in the last hour. Quick read on traffic volume per tenant.
Failures (1h)How many handler failures (transient exceptions) this tenant hit in the last hour. Renders red when non-zero.
DLQ depthHow deep this tenant's dead-letter queue is right now. Renders red when non-zero.
StatusActive / Disabled
ActionsDisable / Enable (mutually exclusive) · Remove · Hard delete

The three traffic columns only appear once at least one tenant is publishing per-tenant metrics. If you don't see them, your monitored service is either single-tenant or its Prometheus scrape isn't emitting tenant_id labels yet (turn that on in the service's Wolverine + OpenTelemetry config). The columns disappear again the moment no tenant has data — they don't sit there showing dashes.

When to drill in

Click any tenant row to expand it. The expand panel shows two cards:

  • Top message types — the top five message types this tenant is processing, ranked by execution count, with per-type failure and DLQ counts. Use this to answer "which handler is the noisy tenant hammering?" before paging the team that owns it.
  • Durable counters — the full breakdown of this tenant's durable-store depth: incoming, scheduled, outgoing, handled, and dead-letter counts. Reach for this when the service-level Durability tab numbers look high and you want to know which tenant is parked in the inbox.

What "normal" looks like:

  • Failures (1h) > 0 — single failures from a transient outage are normal; sustained non-zero across refreshes means the tenant is hitting a real bug or running with bad config.
  • DLQ depth > 0 — anything above zero means messages are stuck. A tenant whose DLQ depth is climbing while the rest are flat is a classic noisy-neighbour signal.
  • Executions (1h) far above the rest — usually fine on its own, but combine it with rising failures and you have a tenant that's about to start affecting the rest of the system.

Lifecycle

The full lifecycle a tenant goes through, from creation through permanent destruction:

  1. Register — the monitored service starts up with MultiTenantedDatabasesWithMasterDatabaseTable (Marten) configured. CritterWatch sees the DynamicMultiple cardinality and renders the Tenants tab.
  2. Add — operator clicks Add Tenant, supplies a tenant id + connection string. CritterWatch dispatches AddTenant; the service writes a master-table row and (best-effort) creates the per-tenant database if it doesn't already exist.
  3. Use — application code opens sessions against the new tenant id and reads/writes per-tenant data.
  4. Disable / Enable — soft toggles via DisableTenant and EnableTenant. Disabled tenants stay in the master table but session-open against them throws UnknownTenantIdException until re-enabled. Useful for incident-time isolation without data loss.
  5. Remove (RemoveTenant) — drops only the master-table row. The per-tenant database is left intact for forensic recovery / backup. Reversible by re-running AddTenant with the same id + connection string.
  6. Hard delete (HardDeleteTenant) — drops the per-tenant database and removes the master row. Permanent; not reversible.

The end-to-end lifecycle is pinned by the multi_tenancy_lifecycle_end_to_end integration test (#103 Pass 1) so any Pass-2 UX change is forced to keep the underlying behaviour stable.

Tenant-id normalization (TenantIdStyle)

When the host sets StoreOptions.TenantIdStyle to ForceLowerCase or ForceUpperCase, both AddTenant and session-open run the supplied tenant id through MaybeCorrectTenantId before doing anything with it. Concrete consequence:

ConfiguredAddTenant("Acme-Corp", …) writesmartenStore.LightweightSession("Acme-Corp") resolves to
CaseSensitive (default)Acme-Corpthe Acme-Corp tenant DB (or fails if you only added acme-corp)
ForceLowerCaseacme-corpthe acme-corp tenant DB
ForceUpperCaseACME-CORPthe ACME-CORP tenant DB

The normalization is silent — the operator's typed id is rewritten without a warning. The Pass-2c UI work uses this contract to inline-warn before the command leaves the browser. Under ForceLowerCase / ForceUpperCase, you cannot have two tenants whose ids differ only in case (the second AddTenant collides with the first).

Actions

ActionEffect
DisableDisableTenant(tenantId) — soft-disables; messages routed to the tenant queue without dropping data
EnableEnableTenant(tenantId) — re-enables a disabled tenant
RemoveRemoveTenant(tenantId) — drops the master-table record only; the per-tenant database itself is not deleted
Hard delete (#68)HardDeleteTenant(tenantId) — drops the tenant database and removes the master record. Permanent.

See Wolverine.CritterWatch / Inbound commands for the full command reference.

Static / sharded case (StaticMultiple) — read-only (#103 Pass 2a)

Tenant lists configured at startup show as a read-only table. The Add Tenant button is absent and per-row actions render as a "Read-only" hint with the tooltip "This service uses static tenancy; tenants are configured at startup, not at runtime." Source: eventStores[*].database.databases[*].tenantIds, flattened and de-duplicated by tenant id; each row carries the DatabaseDescriptor.identifier (or databaseName) so operators see which DB the tenant routes to.

Add Tenant dialog — TenantIdStyle validation (#103 Pass 2c)

The Tenant ID input runs through the resolved TenantIdStyle policy client-side (mirroring JasperFx.MultiTenancy.TenantIdStyleExtensions.MaybeCorrectTenantId):

  • Empty / whitespace-only input is rejected (the Add button stays disabled).
  • When the input would be silently rewritten under ForceLowerCase / ForceUpperCase (e.g. Acme-Corpacme-corp under ForceLowerCase), an inline warning appears under the input. The Add button stays enabled — the operator can knowingly proceed; the warning surfaces the policy rather than enforcing a different one.
  • Under CaseSensitive no warning is shown; the id is stored exactly as supplied.

Hard-delete typed-id confirmation (#68)

Hard delete is gated behind a typed-tenant-id confirmation modal:

The standard "are you sure?" double-confirm is not enough friction for a database-drop. The operator must type the exact tenant id; only then does the dialog's primary action button enable. Audit log records every hard delete with the operator's identity (see Audit Log).


HTTP Tab (conditional)

Only rendered when the service surfaces any HTTP endpoint — either a Wolverine HTTP graph (Wolverine.HTTP is referenced), or non-Wolverine ASP.NET Core endpoints when the host opts in via services.AddCritterWatchHttp().

The tab is one merged surface — Wolverine HTTP chains and non-Wolverine endpoints share the same table with a Source chip per row. A filter strip above the table lets you narrow by source.

All Endpoints

ColumnMeaning
SourceWolverine / MinimalApi / Mvc / RazorPages / SignalR / Other — color-coded chip
MethodGET / POST / PUT / DELETE / PATCH / OPTIONS — color-coded tag
RouteRoute pattern. Multi-version Wolverine chains show one row with a chip per declared version (v1 / v2 / 2024-01-01)
HandlerGenerated handler endpoint or display name (monospace)

Click any row to drill into the per-endpoint detail page. Wolverine chains route to the HTTP chain detail page; non-Wolverine endpoints route to the ASP.NET endpoint detail page.

Above the table, source chips work as a multi-select filter; toggle the ones you care about. The free-text input on the right matches anywhere in the route, method set, or handler name.

A configuration block follows with the Wolverine HTTP graph's settings — global route prefix, antiforgery defaults, applied policies, middleware types, tenant-detection strategies. Tooltip per field, sourced from XML doc comments.

Coming soon: when a route fires a downstream message, you'll see chips on the row pointing at the Messaging-tab page for that message type. The Wolverine HTTP graph surfaces cascading messages on the chain detail page today; the row-level chip is the next step.


Detail-page drill-ins

Several tabs feed into dedicated detail pages with their own routes. Each is breadcrumb-anchored back to the parent service.

Per-node detail page (#108)

Route: /service/:id/node/:nodeNumber

Drilled into from the Cluster tab → Nodes view (clicking the node number cell in either Cards or List view). Surfaces:

  • Node identity: number, version, started time, last health-check timestamp
  • Heartbeat history graph
  • Agents currently assigned to this node, with un-assign / pin controls
  • Per-node alert list filtered to this node
  • Eject control

Per-message-store detail page (#105)

Route: /service/:id/message-store/:storeUri

Drilled into from the Durability tab → message-store row click. Surfaces:

  • Persistence counts (incoming, outgoing, scheduled, handled, DLQ)
  • Recent inbox / outbox samples
  • The actual installed DDL for that store's durability tables (rendered through DdlBlock)
  • DLQ drill-in pre-filtered to this store

Endpoint detail page

Route: /service/:id/endpoint/:endpointUri

Drilled into from the Endpoints tab. Surfaces:

  • Endpoint identity (URI, mode, transport)
  • Live metrics (sent / received / failed / DLQ)
  • Circuit-breaker + buffering settings (read-only — edit happens on the parent tab)
  • Recent message samples
  • The handler source code if the endpoint terminates in a handler

Handler chain detail page

Route: /service/:id/handler/:messageType

Drilled into from the Messaging tab. Surfaces:

  • Message type identity (full type name, namespace)
  • Middleware chain (in order)
  • Generated handler source code with syntax highlighting
  • Per-endpoint sticky-routing metadata
  • Recent metric trend

Saga detail page

Route: /service/:id/saga/:stateType

For sagas declared in the service. Surfaces saga state shape, configured timeouts, retry policy, and a link to Saga Instances for active state rows.

Saga instances page

Route: /service/:id/saga/:stateType/instances

Live list of in-flight saga instances. Per-row: instance id, state, last update, message log.

Subscription / Projection / Broker detail pages

Subscription detail: /service/:id/subscription/:name Projection detail: /service/:id/projection/:shardName Broker detail: /service/:id/broker/:broker

Each surfaces the same shape — identity, config snapshot, recent activity, lifecycle controls (rewind, rebuild, restart). See the linked tabs for full coverage:

  • Projections — Projections
  • Subscriptions — Cluster tab → Agents view
  • Brokers — Storage tab

DDL viewer

A few tabs render PostgreSQL DDL through the shared DdlBlock component. It uses highlight.js with the SQL grammar, light + dark themes, and a copy-to-clipboard affordance. Used on the Storage tab (Document Stores, Event Stores) and the per-message-store detail page.


Operations gating

Every action on the detail page that mutates state — Pause / Drain / Restart, Pin / Unpin, Eject Node, Add / Remove / Hard-delete Tenant, edits — is gated by the global "Operations enabled" flag. When disabled, the buttons render disabled with a hover tooltip explaining why. This is set in Settings → Connection settings, and is the recommended way to switch CritterWatch into a read-only mode for production.

Released under the MIT License.