|
| 1 | +# Dashboard URL search |
| 2 | + |
| 3 | +Homer 11 can open the dashboard with **prefilled search filters** and run a query automatically. This replaces the homer-app Share URL pattern ([homer-app#607](https://github.com/sipcapture/homer-app/issues/607)) with simple query parameters. |
| 4 | + |
| 5 | +Related docs: |
| 6 | + |
| 7 | +- [Search CLI](SEARCH.md) — terminal search and `--proto` names |
| 8 | +- [Mapping examples](../examples/mappings/README.md) — `fields_mapping` JSON per protocol |
| 9 | +- API: `POST /api/v4/transactions/search` — same `filter` object the UI sends |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## URL shape |
| 14 | + |
| 15 | +Use the dashboard hash and put filters in the query string. |
| 16 | + |
| 17 | +**Recommended** — query **before** the hash: |
| 18 | + |
| 19 | +```text |
| 20 | +https://coordinator.example/?from_user=123123&minutes=10#dashboard |
| 21 | +``` |
| 22 | + |
| 23 | +**Also supported** — query after the hash: |
| 24 | + |
| 25 | +```text |
| 26 | +https://coordinator.example/#dashboard?from_user=123123&minutes=10 |
| 27 | +``` |
| 28 | + |
| 29 | +On load the UI: |
| 30 | + |
| 31 | +1. Switches to the dashboard (you must already be logged in, or complete OAuth/login first). |
| 32 | +2. Applies the time window and filter fields to the Protocol Search widget (if present). |
| 33 | +3. Runs search on the first **Results** or **Time Chart** widget. |
| 34 | +4. Strips search parameters from the address bar (filters stay in the widget). |
| 35 | + |
| 36 | +Copy a link from the UI: Protocol Search widget → link icon next to **Clear**. |
| 37 | + |
| 38 | +Implementation: `src/ui/src/dashboard/searchDeepLink.ts`, `SearchDeepLinkBootstrap.tsx`. |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## Query parameters |
| 43 | + |
| 44 | +### Time range |
| 45 | + |
| 46 | +| Parameter | Description | |
| 47 | +|-----------|-------------| |
| 48 | +| `minutes` or `m` | Relative window ending **now** (default **60** when only filters are set) | |
| 49 | +| `from`, `to` | Absolute range, Unix epoch **milliseconds** | |
| 50 | + |
| 51 | +Example — last 15 minutes: |
| 52 | + |
| 53 | +```text |
| 54 | +/?from_user=alice&m=15#dashboard |
| 55 | +``` |
| 56 | + |
| 57 | +Example — fixed window (same as homer-app issue): |
| 58 | + |
| 59 | +```text |
| 60 | +/?from_user=123123&from=1743857605187&to=1743858205187#dashboard |
| 61 | +``` |
| 62 | + |
| 63 | +### Protocol selection |
| 64 | + |
| 65 | +| Parameter | Alias | Description | |
| 66 | +|-----------|-------|-------------| |
| 67 | +| `proto_type` | `proto` | HEP / virtual protocol id (see table below) | |
| 68 | +| `event_type` | `event` | Mapping **profile** name (`call`, `registration`, `default`, …) | |
| 69 | + |
| 70 | +Defaults if omitted: `proto_type=1`, `event_type=call` (SIP calls). |
| 71 | + |
| 72 | +### SIP-oriented filters (built into URL parser) |
| 73 | + |
| 74 | +At least one of these must be present or the deep link is ignored. |
| 75 | + |
| 76 | +| Parameter | Aliases | Maps to API `filter` | |
| 77 | +|-----------|---------|----------------------| |
| 78 | +| `from_user` | `user_from`, `caller` | `from_user` | |
| 79 | +| `to_user` | `user_to`, `callee` | `to_user` | |
| 80 | +| `call_id` | `callid` | `call_id` | |
| 81 | +| `method` | — | `method` | |
| 82 | +| `src_ip` | — | `src_ip` | |
| 83 | +| `dst_ip` | — | `dst_ip` | |
| 84 | +| `limit` | — | `param.limit` (default 50) | |
| 85 | + |
| 86 | +Example — SIP From user, registration profile: |
| 87 | + |
| 88 | +```text |
| 89 | +/?proto_type=1&event_type=registration&from_user=1000&minutes=30#dashboard |
| 90 | +``` |
| 91 | + |
| 92 | +--- |
| 93 | + |
| 94 | +## Built-in protocols (`proto_type` + `event_type`) |
| 95 | + |
| 96 | +These match seeded `mapping_schema` rows and the Search CLI `--proto` table in [SEARCH.md](SEARCH.md). |
| 97 | + |
| 98 | +| Protocol | `proto_type` | Typical `event_type` | URL example (extra params) | |
| 99 | +|----------|--------------|----------------------|----------------------------| |
| 100 | +| SIP calls | `1` | `call` | `from_user`, `to_user`, `call_id`, `method` | |
| 101 | +| SIP (generic) | `1` | `default` | same | |
| 102 | +| SIP registration | `1` | `registration` | `from_user` (AOR/contact in UI) | |
| 103 | +| RTCP | `5` | `default` | `src_ip`, `dst_ip`, `call_id` (payload search) | |
| 104 | +| DNS | `53` | `default` | `src_ip`, `dst_ip`, `call_id` | |
| 105 | +| LOG | `100` | `default` | `call_id` (message text search) | |
| 106 | +| OTLP traces | `200` | `default` | set `proto_type=200`; use UI or API for `trace_id` (see below) | |
| 107 | +| OTLP metrics | `201` | `default` | `proto_type=201` | |
| 108 | +| OTLP logs | `202` | `default` | `proto_type=202` | |
| 109 | +| RTP agent | `34` | `default` | CLI/API; URL uses shared IP / `call_id` fields | |
| 110 | +| Line Protocol | `300` | `schema__table` | e.g. `event_type=main__cpu` (see LP section) | |
| 111 | + |
| 112 | +Examples: |
| 113 | + |
| 114 | +```text |
| 115 | +# RTCP on a subnet |
| 116 | +/?proto_type=5&event_type=default&src_ip=10.0.1.10&minutes=60#dashboard |
| 117 | +
|
| 118 | +# DNS |
| 119 | +/?proto_type=53&dst_ip=8.8.8.8&m=120#dashboard |
| 120 | +
|
| 121 | +# OTLP traces (protocol only; refine in widget or API) |
| 122 | +/?proto_type=200&event_type=default&minutes=15#dashboard |
| 123 | +
|
| 124 | +# SIP registration |
| 125 | +/?proto_type=1&event_type=registration&from_user=sip:user@domain.com#dashboard |
| 126 | +``` |
| 127 | + |
| 128 | +### Line Protocol (`proto_type=300`) |
| 129 | + |
| 130 | +LP rows are created when measurements are ingested (`lp_mapping_sync`). The profile name is **`schema__table`**, for example `main__cpu`. |
| 131 | + |
| 132 | +```text |
| 133 | +/?proto_type=300&event_type=main__cpu&minutes=10#dashboard |
| 134 | +``` |
| 135 | + |
| 136 | +Field filters for LP in the URL are not wired yet; use the Protocol Search widget after the link opens, or the CLI with `--proto lp --event-type main__cpu`. |
| 137 | + |
| 138 | +--- |
| 139 | + |
| 140 | +## Legacy homer-app JSON URLs |
| 141 | + |
| 142 | +homer-app used a single JSON blob as the query string: |
| 143 | + |
| 144 | +```text |
| 145 | +/search/result?{"timestamp":{"from":...,"to":...},"param":{"search":{"1_call":{"user_from":"123123"}}}} |
| 146 | +``` |
| 147 | + |
| 148 | +Homer 11 accepts the same JSON when it starts with `{` (before or as `?q=...`). Known mappings: |
| 149 | + |
| 150 | +- `param.search.<profile>.user_from` → `from_user` |
| 151 | +- `param.search.<profile>.user_to` / `callee` → `to_user` |
| 152 | +- `timestamp.from` / `timestamp.to` → time range |
| 153 | + |
| 154 | +Prefer the flat query parameters above for new integrations. |
| 155 | + |
| 156 | +--- |
| 157 | + |
| 158 | +## API payload (what the URL becomes) |
| 159 | + |
| 160 | +Deep links end up as `POST /api/v4/transactions/search`: |
| 161 | + |
| 162 | +```json |
| 163 | +{ |
| 164 | + "filter": { |
| 165 | + "proto_type": 1, |
| 166 | + "event_type": "call", |
| 167 | + "from_user": "123123" |
| 168 | + }, |
| 169 | + "param": { "limit": 50 }, |
| 170 | + "timestamp": { "from": 1743857605187, "to": 1743858205187 } |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +Field names in `filter` must match the mapping field **`id`** values (see `examples/mappings/fields_*.json`) and what `buildSearchSQLV4` understands in `transactions_v4.go`. |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +## Adding a new protocol (data + UI) |
| 179 | + |
| 180 | +### 1. Ingest and storage |
| 181 | + |
| 182 | +Ensure the node writes the protocol into DuckLake (HEP type or custom table). For HEP types, table suffixes follow `hep_proto_<id>_<profile>` (see [Storage layout](STORAGE_LAYOUT.md)). |
| 183 | + |
| 184 | +### 2. `mapping_schema` row |
| 185 | + |
| 186 | +Add a row so the Protocol Search widget can list fields and the coordinator can build SQL: |
| 187 | + |
| 188 | +1. Copy a seed from [`examples/mappings/`](../examples/mappings/) or create `fields_<hepid>_<profile>.json`. |
| 189 | +2. Each searchable column needs an `"id"` (filter key), `"name"`, `"form_type"`, etc. |
| 190 | +3. Insert via Settings → Mappings UI, or SQL/API into settings DB `mapping_schema`: |
| 191 | + - `hepid` — same integer as `proto_type` in search |
| 192 | + - `profile` — same string as `event_type` in search |
| 193 | + - `hep_alias` — display label |
| 194 | + - `fields_mapping` — JSON array from your file |
| 195 | + |
| 196 | +See [examples/mappings/README.md](../examples/mappings/README.md). |
| 197 | + |
| 198 | +### 3. Backend filter support |
| 199 | + |
| 200 | +In `src/coordinator/handlers/transactions_v4.go`, `buildSearchSQLV4` must translate your field ids into SQL for that table. SIP/RTCP/DNS/LOG/OTLP paths already exist; a **new** HEP type may need a new branch or generic column mapping. |
| 201 | + |
| 202 | +### 4. Dashboard widget |
| 203 | + |
| 204 | +Operators add a **Protocol Search** widget, pick the mapping (hepid + profile) in widget settings, and a **Results** widget. Presets can be registered in `src/ui/src/dashboard/widgets/registry.ts` (`search` extras) and `SearchPanel.tsx` (`SIP_PRESETS`, `OTLP_PRESETS`, …). |
| 205 | + |
| 206 | +--- |
| 207 | + |
| 208 | +## Extending URL parameters for more fields |
| 209 | + |
| 210 | +Today the URL parser only exposes a **fixed set** of query keys (SIP-friendly names + `proto_type` / `event_type`). Protocol-specific ids such as `trace_id`, `service_name`, or `aor` are **not** read from the URL unless you extend the code. |
| 211 | + |
| 212 | +### Option A — Add named parameters (simple) |
| 213 | + |
| 214 | +Edit `src/ui/src/dashboard/searchDeepLink.ts`: |
| 215 | + |
| 216 | +1. Add the field to `SearchDeepLinkSpec` and `TRIGGER_KEYS` if it should alone trigger a search. |
| 217 | +2. Parse it in `parseSearchDeepLink()` from `URLSearchParams`. |
| 218 | +3. Copy it in `buildSearchPayload()` into `filter.<id>`. |
| 219 | +4. Mirror in `buildSearchDeepLinkURL()` and `deepLinkSpecToFormFields()` for the copy-link button. |
| 220 | +5. Add tests in `searchDeepLink.test.ts`. |
| 221 | + |
| 222 | +Example — OTLP `trace_id`: |
| 223 | + |
| 224 | +```typescript |
| 225 | +// parse |
| 226 | +spec.trace_id = p.get('trace_id')?.trim() || undefined |
| 227 | +// payload |
| 228 | +if (spec.trace_id) filter.trace_id = spec.trace_id |
| 229 | +``` |
| 230 | + |
| 231 | +### Option B — Generic `filter.<field>=<value>` (scalable) |
| 232 | + |
| 233 | +Support repeated or prefixed params, e.g. `filter.trace_id=abc` or `f.trace_id=abc`, and merge any `filter.*` key into the API `filter` object after `proto_type` / `event_type` are set. This avoids editing the parser for every mapping field but requires careful validation (allowlist or mapping-driven ids only). |
| 234 | + |
| 235 | +### Option C — Encoded JSON filter (power users) |
| 236 | + |
| 237 | +A single param `filter={"proto_type":200,"trace_id":"..."}` URL-encoded. Same flexibility as homer-app but harder to hand-edit; good for tools generating links. |
| 238 | + |
| 239 | +### Option D — No URL change |
| 240 | + |
| 241 | +Generate links only through the UI copy button after configuring the widget (dynamic mode already serializes all visible fields into the stored form; extending copy-link to emit every configured field id is the smallest UX win). |
| 242 | + |
| 243 | +--- |
| 244 | + |
| 245 | +## Requirements and limitations |
| 246 | + |
| 247 | +| Topic | Detail | |
| 248 | +|-------|--------| |
| 249 | +| Authentication | User must have a valid session/JWT; URL does not embed credentials. | |
| 250 | +| Dashboard layout | At least one **results** or **chart** widget must exist on the active dashboard. | |
| 251 | +| Search widget | Optional; without it, filters still run but the form may not show values. | |
| 252 | +| Custom mappings | URL uses **field ids** from `fields_mapping`; aliases like `user_from` are SIP legacy only. | |
| 253 | +| Dynamic widget fields | Extra mapping fields only appear in the URL after Option A/B/D above. | |
| 254 | +| SQL / AI tabs | Deep links only drive structured **Form** search, not SQL or MCP tabs. | |
| 255 | + |
| 256 | +--- |
| 257 | + |
| 258 | +## Troubleshooting |
| 259 | + |
| 260 | +| Symptom | Check | |
| 261 | +|---------|--------| |
| 262 | +| Dashboard opens, no search | No `from_user` / `call_id` / … trigger param; add at least one filter key. | |
| 263 | +| All rows returned | Filter empty in API body — wrong param name or legacy JSON profile key (`1_call` vs `call`). | |
| 264 | +| Wrong protocol | Set `proto_type` and `event_type` to match the mapping row. | |
| 265 | +| No results widget | Add Results Table from the empty-state buttons in Protocol Search. | |
| 266 | +| homer-app link | Path `/search/result` is not used in Homer 11; use `/#dashboard` and coordinator host. | |
| 267 | + |
| 268 | +--- |
| 269 | + |
| 270 | +## Quick reference |
| 271 | + |
| 272 | +```text |
| 273 | +# SIP by From user, last hour |
| 274 | +/?from_user=123123&minutes=60#dashboard |
| 275 | +
|
| 276 | +# By Call-ID |
| 277 | +/?call_id=abc-def-ghi@host&from=1700000000000&to=1700003600000#dashboard |
| 278 | +
|
| 279 | +# RTCP |
| 280 | +/?proto_type=5&src_ip=10.0.0.1&m=30#dashboard |
| 281 | +
|
| 282 | +# OTLP traces (open correct protocol; add trace_id in UI or extend URL parser) |
| 283 | +/?proto_type=200&minutes=15#dashboard |
| 284 | +``` |
0 commit comments