Synthetics work spanning private-agent tokens, browser private agents,
and job-lease correctness. (Enterprise backend:
openobserve/o2-enterprise#2260.)
## Private-agent probe tokens
- Named, org-level, rotatable `o2syn_` probe tokens — **create / list /
rotate / disable**. Rotate = create-new + keep-old for zero-downtime
overlap; disable = revoke. Fixes the gap where a private-agent token was
an unrevocable standing credential.
- IAM **Synthetics Tokens** page (create / rotate / disable) with an
**Agents** column — how many agents currently use each token, so a token
is only revoked once it's safe.
- `POST /synthetics/agent-tokens` to mint a named token; agent
`token_id` tracking; per-agent **Restart config** action.
## Browser checks on private locations
- Allow browser checks on private, **browser-capable** locations
(previously public/Lambda only).
- Agent-setup drawer emits `--type=browser` so the install command pulls
the browser probe image.
## Private locations as agent pools
- A private location is treated as a **pool of interchangeable agents**
— list shows live/total agent count + C/min (deliberate naming instead
of auto-generated `private-location-XXXX`).
## Lease correctness (P0)
- **Atomic job lease** — no duplicate runs when a location has multiple
agents (guarded `status = 0` claim + winner-scoped return).
- **Capability-filter the lease by agent type** — a net agent and a
browser agent can now share one pool; the net agent no longer wins (and
fails with `unsupported check type: browser`) a browser job.
## Scheduling & RBAC
- Expose `next_run_at` on DueMonitor for drift-free scheduling.
- CREATE-folder query is authoritative (RBAC); require PUT permission to
move checks between folders; pass the folder query param on per-check
API calls.
## What changed
- **MonitorRuns.vue**: Fixed protocol check breakdown cards showing
"Pass Rate by Device" instead of "Duration by Location (Avg)". Added
`OTooltip` with truncation and `cursor-help` on all breakdown card name
spans. Resolved private location labels in the timeline tooltip. Unified
duration formatting to 2 decimal places. Added back the device breakdown
card for browser checks. Fixed `locationDurationBreakdown` to resolve
location IDs to human-readable labels.
- **syntheticResultsSchema.ts**: Renamed device keys `laptop_large` →
`desktop` and `mobile_small` → `mobile` to match the incoming data
schema.
## Why
Protocol check monitors were showing an irrelevant "Pass Rate by Device"
card (device data doesn't exist for protocol checks) instead of the
correct "Duration by Location" card. Browser names and location names in
breakdown cards were untruncated, causing layout issues with long names.
Private location labels in the timeline tooltip displayed raw KSUIDs
instead of human-readable names. Duration values had inconsistent
decimal precision (some clipped, some raw).
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Applies the **Calm Signal** colour language and summary stat strips
across the monitoring + AI-evaluation pages.
- Colourful summary stat strips (`OStatStrip`) with selectable filter
tiles + left row rails / exception washes on **Alerts, Incidents, Eval
Jobs, Scorers, Score Configs**.
- Tile tones match their `OTag` chip colours; critical-first (left)
ordering; re-click a tile to clear the filter.
- AI Observability: consistent section headers, detail drawer, and
per-session signals.
- Migrate `ScorerTypeDialog` to the standard `ODialog`; token-compliance
+ vertical separators on the eval form pages.
- Table perf: lazy tooltips, delegated row shortcuts, virtualization.
The results table sized each virtual row to its own text (w-max), so row
bottom-borders stopped at the end of each line and the shared
histogram+table
scroll container gained a horizontal scroll that dragged the histogram
off,
leaving a blank strip.
- Size the table to its widest rendered row (measured, rAF-throttled) so
every
row spans the full scrollable width and borders run edge to edge; the
unwrapped source cell keeps its full text (no ellipsis) and scrolls.
- Pin the histogram and give the logs table its own scroll box (drop the
delegated scroll-el); both scrollbars now belong to the table, so
horizontal
scroll can't move the chart. Patterns view keeps the combined scroll.
- Remove the window-derived width prop and the dead tableRowSize
measuring loop.
- Bump histogram grid.top 5 -> 8 so the top y-axis label isn't clipped.
## The bug
Opening a **shared link** to an SQL search intermittently lands on an
**empty query editor** ("SQL query is missing or invalid"). Recurring
Logs-Features alpha flake: `monaco-query-prefill` P0 / `shareLink` P1 →
*"query editor never contained SELECT."*
Follow-up to #13414 — that PR guarded a plausible path (the
`fullSQLMode` watcher's "switch ON"), but the trace after it deployed
showed the query **still** dropped. This finds and fixes the actual
cause.
## Root cause (trace-confirmed)
The Monaco query editor is **lazy-loaded**. After the short-URL restore
sets the SQL query, the editor fires its change callback with an empty
`""` **while it mounts — before the restored value is applied.**
`SearchBar.updateQueryValue()` treated that transient empty as a real
edit:
```js
searchObj.data.editorValue = value; // "" overwrites the restored query
searchObj.data.query = value;
if (value.trim() === "" && searchObj.meta.sqlMode === true) {
searchObj.meta.sqlMode = false; // → SQL mode auto-detects OFF → editor cleared for good
}
```
Under load the editor's empty emission lands *after* the restore, so the
query is lost. The trace shows the URL ending `sql_mode=false` with the
`query` param gone — exactly this.
## The fix — state-based, not a timer
The editor emits `""` *because it isn't ready yet*. So we gate on
**readiness (state)**, not a wall-clock wait (a fixed delay would just
change the odds, like "make the env faster"):
- `restoreUrlQueryParams()` sets `meta.pendingUrlQueryRestore = true`
when it restores a non-empty query.
- `updateQueryValue()` **ignores an empty emission while that flag is
set**, and clears the flag the instant the real (non-empty) value lands
— so genuine user clears *after* load still work. This mirrors the
existing `loadingStream` guard already in that function.
Because it keys off state, the race can't beat it: no matter *when* the
empty emission arrives, it's ignored until the real value is in.
## Verification
- ✅ **New real unit test** on `restoreUrlQueryParams`
(`useLogs.spec.ts`, actual composable) — asserts the base64 query
decodes into state, SQL mode restores, and `pendingUrlQueryRestore` is
set. **7 passed.**
- ✅ The guard is deterministic by construction (reads the same shared
`searchObj.meta`).
- ⚠️ The load-race is only observable against a **deployed** frontend,
so the final "flake gone on alpha under load" confirmation is a re-run
once this is deployed to alpha. (I was wrong once here — so I'm being
explicit that this last step is pending, not assumed.)
## Note
#13414's guard is left in place — it's a valid defensive symmetry fix,
just not the operative cause. This PR is the operative fix.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
### Empty state for checks with no runs
- When `totalRuns === 0`, the entire MonitorRuns content area (tabs,
KPIs, charts, table) is replaced by a hero `OEmptyState`
- Distinguishes "never triggered" (shows "Trigger a run now" action)
from "no runs in this time window" (shows "Jump to latest data" action)
- Default time range is always "Last 15 minutes" — drops stale
query-param anchoring
### Consolidated check fetch
- Moved the check API call to `MonitorResults.fetchCheck()` on mount,
removing the duplicate `resolveMonitorType()` in `MonitorRuns`
- `isBrowser` is now a computed deriving from the `checkType` prop
- Removes `need-check-data` emit/watch pattern — parent always fetches
on mount
### Org routing fix
- `SyntheticMonitoring.vue` and `PrivateLocationDetail.vue` now carry
`org_identifier` when navigating to `synthetic-monitor-results`
### Location label fix
- `MonitorRuns.vue` and `RunDetail.vue` use `loc.label` instead of
`loc.name` (matches `SyntheticLocation` type)
## Changes
- `MonitorRuns.vue` — page-level empty state gate, `checkType` prop,
`isBrowser` computed, removed `resolveMonitorType()`/watch
- `MonitorResults.vue` — `fetchCheck()` on mount, `checkType` ref,
removed `need-check-data` listener
- `SyntheticMonitoring.vue` — carry `org_identifier` in `openDetail`
- `PrivateLocationDetail.vue` — carry `org_identifier` in `openMonitor`
- `RunDetail.vue` — `loc.name` → `loc.label`
- `MonitorResults.spec.ts` — updated props and mock descriptions
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What
On the traces detail page, make **Waterfall** the first tab and the
default landing view (previously Flame Graph).
## Why
Waterfall is the more universally useful default for inspecting a
trace's span timeline; users landing on Flame Graph first had an extra
click to get to it.
## Changes
- `TraceDetails.vue`: reorder `TRACE_TAB_DEFS` so `waterfall` leads
`flame-graph`, and set `DEFAULT_TRACE_TAB` to `"waterfall"`. The
fallback watcher and reorder/persistence logic reference these, so they
follow automatically.
- `TraceDetails.spec.ts`: updated tab-order / default / fallback /
reorder assertions for the new default order.
## Behavior note
Tab order and the active tab are persisted per-browser in `localStorage`
(`o2_trace_tab_order` / `o2_trace_active_tab`). Users who have already
opened a trace keep their saved preference — the new Waterfall default
applies to browsers with no stored preference.
## Testing
`TraceDetails.spec.ts` + `TraceDetails.missing-span-kind.spec.ts` — 199
passed / 3 skipped.
Three related synthetics RBAC fixes (frontend + backend handlers).
Read-path and write-path folder isolation, all mirroring how alerts
already works.
## Fixes
- **Per-check calls now send `?folder=`** — the UI wasn't passing the
check's folder on per-check API calls
(`get`/`run`/`enable`/`getRuns`/`getRun`/`presignArtifacts`/`artifactUrl`),
so folder-scoped RBAC couldn't resolve. Threaded `route.query.folder`
through the service + all call sites; also fixed `update()` which
computed the folder param but never appended it. Matches
`web/src/services/alerts.ts`.
- **Move requires PUT permission** — the `move_synthetics` handler did
no permission check and didn't even extract the user, so a
List+Delete-only role could move checks between folders. Now runs
`check_permissions(id, org, user, "synthetics", "PUT", …)` per check
before moving → 403 if any fails. Mirrors alerts' `move_to_folder`.
- **Create is folder query-authoritative** — the POST gate checks
`?folder=`, but the handler wrote to `body.folder_id`, so a crafted body
could create a check in a folder the user can't access. The `?folder=`
query is now authoritative (`body.folder_id` ignored for the
destination), exactly like regular alerts' `create_alert`
(`get_folder(query)`).
## Notes
- Backend fixes are OSS-only — `check_permissions` lives in
`openobserve-core`, so gating in the handlers is the correct place; no
enterprise change.
- The anomaly-detection alert create has the same body-vs-query gap as
the synthetics create fix — tracked separately in #13416 (fix later).
- Pairs with the o2-enterprise default-folder registration fix (#2247).
> **This PR contains two independent logs changes:**
> **(A)** a product fix for the shared-link SQL-query restore race, and
> **(B)** the log-detail default-tab test alignment (original scope of
this PR).
---
# (A) fix(logs): preserve restored SQL query on shared-link open
(fullSQLMode watcher race)
## The bug
Opening a **shared link** to an SQL search **sometimes lands on an empty
query editor** ("SQL query is missing or invalid") instead of the shared
search. Intermittent, worse under load — it's the recurring
Logs-Features alpha flake (`monaco-query-prefill` P0 "query editor never
contained SELECT").
**Not the short-URL mechanism** — the short URL correctly carries
`sql_mode=true` + the base64 query. The loss happens in the browser
during restore.
## Root cause — a watcher race
`restoreUrlQueryParams()` raises `shouldIgnoreWatcher`, flips
`meta.sqlMode = true`, then writes the decoded query into the editor.
Flipping `sqlMode` fires the **`fullSQLMode`** watcher in `Index.vue`,
whose two branches were **asymmetric**:
- **switch OFF** branch → already checks `shouldIgnoreWatcher` ✅
- **switch ON** branch → **did not** ❌ — it called `setQuery()`,
rebuilding a *default* `SELECT … FROM stream` over the value the restore
was mid-writing.
Under load the watcher wins the race; the editor briefly empties, SQL
mode auto-detects back **off**, and the editor is cleared entirely.
## The fix
Make the "switch ON" branch honour `shouldIgnoreWatcher` too — symmetric
with "switch OFF". During any programmatic restore the watcher **stands
down** and the restored query is the last word. (`shouldIgnoreWatcher`
is raised only by programmatic-state paths whose purpose is to suppress
watcher side-effects, so honouring it here is consistent.)
## Verification (A)
- ✅ **New unit test**: with `shouldIgnoreWatcher` set, flipping
`sqlMode` ON does **not** call `setQuery`/`updateUrlQueryParams`, and
the restored `SELECT` query is preserved.
- ✅ Existing `fullSQLMode` watcher test still passes; full
`Index.spec.ts` green (**69 passed**).
- ✅ Reproduced the failure on **alpha under CI-load** beforehand
(share-link P0 flaky, "query editor never contained SELECT").
- ⚠️ The race is only observable against a **deployed** frontend (the
alpha e2e suite runs against alpha's frontend, not a local build), so
the final "flake gone on alpha under load" check requires this deployed
to alpha, then re-running the Logs-Features share-link tests.
Files: `web/src/plugins/logs/Index.vue`,
`web/src/plugins/logs/Index.spec.ts`.
---
# (B) test(logs): align log-detail default-tab test with Table default
(13368)
## What
Updates the log-detail sidebar default-tab regression test (originally
Bug #9724) to match the **intentionally shipped** behavior from [PR
#13368](https://github.com/openobserve/openobserve/pull/13368) — *"logs
sidebar table will be default view and draggable tabs"*.
## Why
#13368 changed the log-detail sidebar default from the **JSON** tab to
the **Table** tab (`SearchResult.vue` — `detailTableInitialTab.value =
"table"`). The Bug #9724 test still asserted JSON-as-default, so it
fails on `main`: the JSON tab's `data-state` stays `inactive` for the
full timeout across all retries, on both OSS and ENT Logs-Regression.
Not a flake and not a product bug — the test expectation was simply out
of date after a deliberate default-view change.
## Changes
- Rename test → **"Log detail sidebar opens with Table tab by default
(#13368)"**.
- Add `verifyTableTabSelectedByDefault()` page-object helper
(auto-retrying `toHaveAttribute('data-state','active')`); remove the
now-invalid `verifyJsonTabSelectedByDefault()`.
- Reorder switch steps: assert Table default (+ wrap toggle, which
renders only on the Table tab), switch to JSON and back to Table, reopen
and confirm Table default persists.
## Note on #13407
Supersedes the earlier heal
[#13407](https://github.com/openobserve/openobserve/pull/13407), which
misdiagnosed the failure as a `data-state` timing flake. A
locally-hosted stale pre-#13368 build had masked the change.
Files: `tests/ui-testing/pages/logsPages/logsPage.js`,
`tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-regression.spec.js`.
```
npx playwright test playwright-tests/RegressionSet/Logs/logs-regression.spec.js \
-g "Table tab by default" --project=chromium
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
- Collapse all config sections by default (was expanded)
- Add open-state accent to OCollapsible sidebar variant: left accent
bar, tinted header, and accent-colored icon/chevron when expanded
- Move sidebar chevron to the right of the label (rotates on open)
- Give every config section a leading icon via SECTION_ICONS
- Add theme-aware collapsible open-state tokens (light + dark)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Fixes the Agent Graph end-to-end — UI (openobserve/web) + service-graph
backend (openobserve/core). Root causes were found and validated against
live trace data.
---
## 1. UI: wrong graph until refresh + no independent graph-type selector
The Agent Graph page embeds Traces' `ServiceGraph`, which read its
viz/layout type from the shared `searchObj.meta` singleton and
auto-loads on mount.
- **Wrong graph on return (from Agent Behavior):** the effective stream
fell back to the literal `default` trace stream before `loadAgents()`
resolved, so ServiceGraph rendered that whole stream's service topology
instead of the agent graph. Fixed: no `default` fallback in agent mode;
render is gated behind `graphReady` (a resolved agent stream), with a
spinner / "no agents" empty state otherwise; stale agent/stream
selections reconciled on reload.
- **No graph-type selector / inherited the Traces tab's type:** added
`vizType`/`layoutType` prop overrides on `ServiceGraph` (computeds
prefer props, else the shared store — Traces behavior unchanged), and
gave the Agent Graph page its own Tree/Graph + layout selector with its
own state and `agentGraph_*` localStorage keys.
- **Stream picker** now lists only the distinct `source_stream`s of
discovered agents (not agentless streams), annotated with per-stream
agent counts, with an option tooltip and wider control.
New `AgentGraphPage.spec.ts` (5 tests): gating, empty state, viz/layout
independence, stream-picker scoping. `vue-tsc` app + vitest typecheck
clean.
## 2. Backend: agent→model/tool edges mis-attributed to the service
Model/tool spans (`chat`, `execute_tool`) don't carry
`gen_ai.agent.name` — it lives on an ancestor `invoke_agent` span
(confirmed as the norm across the OTel GenAI spec, Google ADK, OpenAI
Agents SDK, OpenInference, CrewAI, LangGraph). The edge builder climbs
≤4 parent hops to find it, else falls back to `service_name`. When an
intermediate span is missing (the spec permits omitting `invoke_agent`;
inconsistent sampling / async context loss produce orphan LLM spans),
the climb fails and the span mis-attributes to the service — a spurious
`service → model` / `service → tool` edge.
Fix: add a per-trace agent CTE (`MAX(gen_ai_agent_name) GROUP BY
trace_id`) as a fallback level in the COALESCE, between the ancestor
climb and the `service_name` fallback, for both edge queries. This
matches the industry-recommended fallback (group by trace when the chain
breaks).
### Validation (live introspect data, 2 streams, 7-day window)
| stream | before | after |
|---|---|---|
| `sre_agent_traces_production` | `service → model` (47+3 reqs) |
eliminated → o2_ai_agent (247) / sre_rca_agent (29) |
| `sre_agent_traces_production_us2` | `service → model` (20 reqs) |
eliminated → o2_ai_agent (279) |
- No cross-agent contamination in the multi-agent stream (traces are
single-agent). No tool-edge regression.
- Non-agent traces: CTE returns nothing → NULL join → falls to
`service_name` exactly as before (no behavior change).
- Perf: single-column `GROUP BY trace_id` aggregate, ~+25% wallclock on
the batch edge query; runs in the hourly job, not a hot path.
Unit tests cover the COALESCE position and CTE builder.
## Not included / follow-up
- **env/version in the agent dropdown** requires backend extractor + DB
migration + API changes. Tracked separately.
## Note
Full enterprise `cargo build` isn't runnable in the dev environment (the
`o2_enterprise` crate is a resolution stub); the SQL-builder logic is
verified via standalone rustc and the runtime behavior via the live
dry-run above. CI runs the real enterprise build.
## What changed
- **Warning status consistency**: The backend returns 4 run statuses
(`passed`, `warning`, `failed`, `error`) but the frontend only handled
`passed`/`failed` consistently. Across `MonitorRuns.vue`,
`SyntheticMonitoring.vue`, `syntheticResultsSchema.ts`, and
`useSyntheticResults.ts`, all 4 statuses are now properly mapped to
their own badge variants, filter options, KPI cards, timeline segments,
and breakdowns.
- **Cooldown period label**: Removed the `*` required indicator from the
cooldown period label in `CheckAlerts.vue` since the field is optional.
## Why
The TLS certificate-expiring-soon check returns `warning` status, which
appeared inconsistently: green "passed" in some views, red "failed" in
others, or entirely missing from filter dropdowns. The fixes ensure SREs
can distinguish "slightly flaky check" from "service is crashing" at a
glance.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Enable Windows agent setup tab (install.ps1 + published binary now
exist).
- Rename location `name` → `label`; Private Locations list now shows the
last-seen agent's name instead of a raw version string.
- Fix back button on the location detail page silently no-opping (wrong
route name).
- Fix Private Locations tab showing 0 rows after back-navigation (fetch
raced the org-identifier hydration).
- Add a per-agent "Recover" action that pre-fills `--agent-name` in the
setup command.
- Drop the live/total ratio and C/min columns from the list — pool stats
live on the detail page.
## What changed
Adds a "Synthetics Locations" settings panel for managing public probe
locations (AWS, GCP, Azure, custom). Includes four new components —
`SyntheticsLocationsList` (OTable-based list with search, bulk actions,
import/export), `SyntheticsLocationForm` (ODrawer + OForm + Zod for
create/edit), and `ImportSyntheticsLocations` (JSON upload/textarea with
live validation). Extended the frontend service with `createLocation`,
`updateLocation`, `deleteLocation`, and `bulkDeleteLocations` methods.
Registered a new `synthetics_locations` route under the enterprise
feature flag and added a "Synthetics" group to the AppSettings sidebar.
Shortened import/export action labels from "Import Locations"/"Export
Locations" to "Import"/"Export".
## Why
The backend already exposed full CRUD endpoints for synthetics locations
(`POST/PUT/DELETE /api/{org}/synthetics/locations`), but there was no
administrative UI for managing them. This panel gives meta-org (root)
users the ability to create, edit, delete, enable/disable, import, and
export public probe locations without manual API calls.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What changed
Replaces `chrome.runtime.*` direct messaging (which requires a static
`externally_connectable` manifest entry) with a `window.postMessage`
bridge transport for the synthetics recorder extension. This removes
`externally_connectable` from the extension manifest so the recorder
works with self-hosted OpenObserve instances on any domain. Also
includes fixes for recording tab lifecycle handling, replay proxy
deserialization, and tab removal edge cases.
## Why
The `externally_connectable.matches` field in the manifest is static —
it cannot include arbitrary self-hosted domains, so users hosting
OpenObserve on their own URLs couldn't use the extension recorder. The
bridge transport (content script + `window.postMessage`) decouples the
web app from the extension's manifest, letting any origin communicate
with the recorder.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What changed
- Added header-level tab navigation (Checks / Private Locations) to the
Synthetic Monitoring page using `OPageLayout`'s `#header-tabs` slot with
`tabs-below`, rendering tabs on a dedicated second row.
- Made the header action button conditional: "New Check" on the Checks
tab, "Setup an agent" on the Private Locations tab.
- Removed the `@setup` emit and "Setup an agent" button from the
`PrivateLocations` table toolbar — that action now lives in the page
header.
- Removed the `#tabs` slot (type toggle group) and `onEmptyAction`
handler from `PrivateLocations`.
- Added 14 new tests for `SyntheticMonitoring` (25 total) and created a
new `PrivateLocations.spec.ts` with 35 tests.
## Why
The user reported that `header-tabs` was not rendering on a second row —
investigation revealed the `tabsBelow` prop was missing. Separately, the
"Setup an agent" action needed to appear contextually in the page header
when viewing Private Locations rather than as a persistent button in the
table toolbar.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What
Two gaps in **Data Sources → Recommended**, both cases where the agent
scripts already existed but nothing in the product reached them:
1. **macOS was missing entirely.** `openobserve/agents` ships
`mac/install.sh`, but Linux and Windows were the only host agents in the
UI, so Mac users had no path from the product.
2. **No uninstall anywhere.** `linux/uninstall.sh`,
`windows/uninstall.ps1` and `mac/uninstall.sh` all exist and were
reachable from nowhere.
## macOS card
Sits between Linux and AWS in the sidebar, reusing the existing
`osAgent` scaffolding (install code block, host-metrics detection,
shared troubleshooting). Two deliberate differences from its siblings:
- **No generic/EC2 toggle.** There is no `mac/ec2` script, so it is a
single command — and the EC2-only troubleshooting row ("hosts show up
under an instance id") is dropped, since it cannot apply.
`sharedAgentTroubleshooting` gained an `includeEc2` flag; Linux/Windows
behaviour is unchanged.
- **The unified log needs a bridge.** It is not a file any collector can
tail, so the installer registers a second launchd daemon piping `log
stream --style ndjson` into the agent over loopback TCP. Troubleshooting
covers that daemon specifically, plus the volume knobs and the fact that
an *empty* `collector.err` is expected rather than a symptom.
## Uninstall
New `extras.uninstall`, rendered as the **last** collapsed accordion —
below troubleshooting — so a destructive action stays clear of the setup
flow while living on the page that installed the thing.
I deliberately did not reuse `extras.advanced`, whose documented
contract is "an alternative manual path to the *same* result"; uninstall
is the opposite, and Kubernetes already uses that slot for its manual
Helm sequence.
One script per OS covers both the generic and EC2 installs (there are no
EC2-specific uninstall scripts). All three raw URLs verified to return
200.
## Icon
`images/common/macos.png` is a neutral-grey Apple mark derived from the
existing `rum/mac.png`. That asset is solid black, and the sidebar tab
has no dark-mode inversion mechanism (unlike variant icons, which have
`iconInvertDark`), so a black mark would have been invisible in dark
mode. One asset that reads on both themes.
## Testing
- **2,410 tests pass across 749 suites**, including 6 new macOS content
tests and 4 new renderer tests.
- One renderer test mounts a card whose *only* extra is `uninstall`,
which catches forgetting the `.c-more` visibility guard.
- Extended the existing guard that every accordion icon is registered in
`OIcon` (an unregistered name silently degrades to raw text beside the
label) to cover the new `delete-outline`.
- `npm run type-check`: 0 errors. `eslint`: clean.
- `routeTabMaps.spec.ts` had a hard-coded `recommended has 9 entries` →
updated to 10. That is the only pre-existing assertion touched.
**Not verified:** I have not seen this render in a browser — it needs a
running backend and login. Worth a reviewer eyeballing the tab, the icon
contrast in dark mode, and the accordion order.
## Found while here (not fixed in this PR)
Two pre-existing bugs in the adjacent card content, both predating this
change:
1. **Linux and Windows cards name a service that does not exist.** They
tell users to run `systemctl status openobserve-agent` / `Get-Service
-Name openobserve-agent`, but the installers register
**`otel-collector`** (`linux/install.sh:114,132`,
`windows/install.ps1:31`) — `openobserve-agent` is the Linux *user
account*, not the service. So the primary "most likely fix" snippet on
both cards returns "Unit openobserve-agent.service could not be found".
The new macOS card uses the real launchd labels.
2. **`windows/uninstall.ps1` never deregisters the service** (agents
repo). It stops the service and deletes the install dir but has no
`sc.exe delete`, so the registration survives pointing at a deleted
binary — and since `windows/install.ps1` calls `New-Service` without
removing an existing one, uninstall → reinstall fails. The accordion
copy describes what the script actually does, not what it ought to.
Happy to take both in follow-ups.
## What changed
Fixes UI issues in OpenObserve's Synthetics monitoring section: label
corrections ("Synthetic" → "Synthetics"), validation feedback (toast +
OForm thread-through), step editing data-loss bugs (wire sync,
buildWireFromStep), replay banner cleanup, retries extraction into its
own component, and URL display in RunDetail step expansion.
## Why
Each fix addresses a specific user-reported or observed defect — steps
that silently dropped edits on save, validation errors that gave no
visual feedback, a banner that persisted after deleting all steps, and
incorrect label/URL display in sidebar, headers, and run detail views.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replaced ChartRenderer with PanelSchemaRenderer in MetricCardChart.vue
for consistent rendering.
- Updated MetricCardChart.spec.ts to assert the panel schema and
injected data correctly.
- Introduced applyMeasuredYAxisLeftInset utility to adjust y-axis label
spacing based on content width.
- Enhanced convertPromQLData to call applyMeasuredYAxisLeftInset before
returning options.
- Added tests for applyMeasuredYAxisLeftInset to ensure correct behavior
with various configurations.
- Implemented compact preview adjustments in heatmap charts to optimize
rendering in metrics explorer cards.
Config APIs resolve after mount and insert/remove menu tiles in
linksList, shifting the active tile. The selection pill's ResizeObserver
never fires because the flex-constrained list doesn't resize, leaving
the pill over the wrong tile (e.g. Dashboards while on Streams).
- Gate the rail behind menuReady so it paints once its item list is
settled, avoiding config-driven pop-in and layout shift.
- Re-measure the selection pill when the rail's items change.
---------
Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
Companion to the o2-enterprise change. On a live instance the Agent
Behavior/Graph views were empty because the rollup never re-processed
the agent trace streams, and the stream pickers offered non-LLM streams.
service_graph/processor.rs — discovery gap: the rollup discovered
streams from a usage-windowed query, falling back to the schema cache
only when usage was ENTIRELY empty. So any stream that dropped out of
the current window's usage (producer paused, bursty ingest) was silently
skipped forever on a busy instance — exactly how the agent streams went
dark. Replace the all-or-nothing fallback with a union: usage-active
streams ∪ every schema-cache trace stream (deduped). Idle windows are
cheap no-ops, so this doesn't widen the worst case.
agent_signals/processor.rs — always-on + stream-level LLM gate: drop the
removed O2_AGENT_SIGNALS_ENABLED check, and add an early return that
skips streams whose schema has no gen_ai_operation_name (mirrors the
service-graph has_gen_ai gate) so non-LLM streams cost nothing. Thread
has_agent_id into the SQL builders for the schema-gated agent-id
predicate.
AgentGraphPage/AgentBehaviorPage.vue — filter the stream picker to LLM
streams (settings.is_llm_stream !== false), matching LLM Insights /
Sessions. A plain service trace stream has no agents or signals to show
here.
- **add session and travel level evals fix quality page to show scoped
scores improve quality page to allow drill down of scores improve the
quality page UI to make it more indicative of health of agents.**
- **Fix Quality dashboard runs, show agent ids**
- **nats at least once delivery semantics + quality runs page changes**
- **feat(evaluations): improve online evaluation workflows**
---------
Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
## What
Fixes broken search on the **Pipeline Destinations** page (Management >
Pipeline Destinations — "External targets for pipeline output"). Typing
any query left the full list visible; search filtered nothing.
## Root cause
Destinations without a type/URL/method render as **"—"** in the list and
have those fields as `null`. `filterData()` called `.toLowerCase()` on
them directly:
```js
rows[i]["destination_type_name"].toLowerCase() // null → TypeError
```
Any search term that didn't match such a row's name reached this line
mid-iteration and threw a `TypeError`. The exception aborted the
`visibleRows` computed, which then kept its last value — the **full
unfiltered list** — so search appeared to do nothing regardless of the
query. A random non-matching string (the common case) triggers it every
time, since it forces evaluation past every null-type row.
## Fix
Guard each field with a `|| ""` fallback before `toLowerCase()` in
`filterData()`.
## Test
Added a regression test covering a **non-matching** query over a row
with null `type`/`url`/`method` (this is what reproduces the crash — a
matching-name term short-circuits the `||` before touching the null
field). Verified it **fails without the source fix** and **passes with
it**. Full spec: 36/36 passing.
Fixesopenobserve/o2-enterprise#2234🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
## Summary
- add shared lifetime AI credit limits and persisted per-organization
usage tracking
- expose used and total AI credits in organization management with an
editable limit dialog
- keep router quota caches synchronized and delegate billing and
contract exhaustion policy to enterprise
- clean up the redundant quota table only in non-cloud builds while
preserving cloud credit data
## Companion PR
- https://github.com/openobserve/o2-enterprise/pull/2208
## Testing
- cargo check --features cloud
- cargo test -p openobserve-core --features cloud trial_quota::tests
--lib
- cargo test -p infra --features cloud
m20260717_000001_drop_trial_quota_usage_for_non_cloud::tests --lib
- cargo test -p infra
m20260717_000001_drop_trial_quota_usage_for_non_cloud::tests --lib
- cargo fmt checks for changed Rust packages
- npm run type-check
- 200 focused frontend unit tests covering organization management,
billing usage, organization services, and required numeric input
- live organization-management verification with per-org 0 used / 1,000
total values