This adds a UI and API based trigger for re triggering license check
limits, so we do not have to restart nodes
---------
Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Currently, on the roles edit page, the frontend makes role permission
api for each and every resources. So, for current 41 resources, there
are 41 concurrent api calls, each going through the openfga rbac check
middleware. That means, there are 41 concurrent redundant openfga
checks, and then 41 openfga query to get the role permissions.
This pr adds another api `org_id/roles/role_name/permissions` that
combines all the resource permissions of the given role name and return
the same eliminating the need for making api calls for each rbac
resource.
## What changed
Adds stream-change confirmation dialogs across Traces tabs. When a user
changes the stream in **Service Graph** or **Services Catalog**, the
change is no longer applied silently — instead an `ODialog` prompts the
user to confirm, since changing the stream clears the existing query in
the Traces/Spans tabs.
## Why
Previously, stream selection was only synced one-way: changing the
stream in Traces/Spans propagated to all other tabs. Changing the stream
in Service Graph or Services Catalog did not sync back because those
tabs don't show a query — so doing so could erase an active query in
Traces/Spans without user awareness. This change ensures the user is
explicitly asked before clearing their query.
## Why
The custom spinner-based loading states were inconsistent across traces
views anddid not align with the logs TenstackTable skeleton pattern
already established inthe codebase. The new skeleton provides a unified,
low-friction loading experience — shimmer pills at column-aligned
positions that mirror real cell widths — and removes the need for each
consumer to write its own loading template.
## Data Sources - Linux & Windows Agent Installer Improvements
### What changed
**LinuxConfig.vue** and **WindowsConfig.vue** have been updated to
support environment-specific installation options. Both now show a
two-card selector so users can pick their deployment target before
copying the install command.
---
### Linux
Added an **AWS EC2** option alongside Generic Linux.
- Two clickable selection cards: Generic Linux and AWS EC2
- Switching cards updates the install command automatically
- EC2 card uses the new `linux/ec2/install.sh` script which includes:
- `resourcedetection/ec2` processor to read EC2 instance metadata
- `resource/hostname` processor to set `host.name` from the EC2 Name tag
- Amber prerequisite callout shown when EC2 is selected, explaining that
an IAM role with `ec2:DescribeTags` and `ec2:DescribeInstances` is
required
---
### Windows
Added an **AWS EC2** option alongside Generic Windows.
- Same two-card selection pattern as Linux
- EC2 card uses the new `windows/ec2/install.ps1` script
- Amber IAM prerequisite callout shown when EC2 is selected
- Updated Generic Windows to use the improved `install.ps1` which:
- Bumps OTel version from 0.90.1 to 0.111.0
- Switches download source back to official GitHub releases
- Removes deprecated `memory_ballast` extension
- Fixes processor perf counter instances from hardcoded `[1, 2]` to
`"*"` for all cores
---
### Agent scripts (openobserve/agents repo)
- `linux/ec2/install.sh` - new EC2-specific Linux installer
- `windows/install.ps1` - updated with OTel 0.111.0 and config fixes
- `windows/ec2/install.ps1` - new EC2-specific Windows installer
---
### Test plan
- [ ] Open Data Sources > Recommended > Linux, verify two cards render
correctly in light and dark mode
- [ ] Select EC2 card, verify IAM callout appears and command URL
changes to `linux/ec2/install.sh`
- [ ] Open Data Sources > Recommended > Windows, verify same behaviour
- [ ] Select EC2 Windows card, verify command URL changes to
`windows/ec2/install.ps1`
- [ ] Verify Generic selection on both shows the original install script
URLs
---------
Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
## What changed
Fixed three interrelated issues in the Traces:
(1) duplicated search calls caused by redundant watchers and
programmatic triggers, and (3) `isDatetimeChanged` returning `false`
when switching from absolute to relative mode.
## Why
Separately, loading Traces in any mode triggered 2 redundant search API
calls — watchers on `searchObj.data.query` duplicated explicit trigger
calls at every query writer, mount-time filter restoration also called
`searchData()` despite `loadPageData()` already owning the initial
search, and the DateTime picker's mount replay fired a false positive
"change" that triggered an auto-run.
## Approach
- Added a `markProgrammaticDateChange()` / `userChangedValue` flag to
`DateTime.vue` so the SearchBar can distinguish user-driven date changes
from programmatic ones (redirect, metrics brush, mount replay), using
`nextTick` to reset the flag after the flush.
- Removed the `watch` on `searchObj.data.query` and the debounced
auto-run watchers on query/datetime — every writer of `data.query` in
the codebase already calls a search explicitly, so a watcher doubled
every trigger.
- Removed the `searchData()` call inside the `restoreFilters` block on
mount; the single mount search is owned by `loadPageData()`.
- In `SearchBar.vue`, gated the live-mode auto-run on `userChangedValue
=== true` and `datetimeChanged`, and removed the `searchObj.data.query =
newValue` writes in `applyFilters`/`removeFilter` (filter operations
already trigger search via emit).
- Fixed `isDatetimeChanged` to return `true` when the previous type was
`absolute` and the value is now `relative`, so switching mode always
registers as a change.
## Summary
Fixes duplicate error notifications when NL-to-SQL generation fails due
to 403. The `throw` in `generateAndUpdateQuery` was caught by the catch
block which showed a second generic "Failed to generate SQL query"
notification on top of the correct Unauthorized message. Now returns
early for auth errors, matching the pattern used in alerts.
## Before
User sees two notifications:
1. "Unauthorized Access: You are not authorized..."
2. "Failed to generate SQL query. Please try again."
## After
User sees only:
1. "Unauthorized Access: You are not authorized..."
## Test plan
- [ ] User without AI access sees only one error notification (the
unauthorized message)
- [ ] Non-auth errors still show the generic "Failed to generate SQL
query" message
- [ ] Auth errors don't trigger SearchBar error handling via re-throw
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## What changed
Fixed three issues in the Trace Graph:
- **Multi-root traces no longer crash** — traces where root spans
belonged to distinct services, the same service, or had cyclic service
relationships now render correctly instead of showing an empty graph.
- **Overlapping node labels are truncated** — long service names in
chart nodes are capped at 24 characters with an ellipsis, preventing
overlap with the next tree level.
- **Tooltip now shows the service name** — the pattern node tooltip
header was missing the service name, making it impossible to seethe full
name of a truncated node on hover.
Also added `truncateText` utility to `zincutils.ts` and comprehensive
test fixtures covering all multi-root scenarios.
Adds support to show tentative cost in usage page in cloud
---------
Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: Yashodhan Joshi <yjdoc2@gmail.com>
## Summary
When the AI chat endpoint returns HTTP 403, `generateSQL` returned
`null` without setting `streamingResponse`, so `CodeQueryEditor` fell
through to the generic "Failed to generate SQL query" message. Now sets
`UNAUTHORIZED_MESSAGE` so the UI displays the proper error.
## Why
The earlier auth fix (#12558) only handled SSE error events inside the
stream. But when the middleware blocks the request with HTTP 403,
`!response.ok` triggers before the SSE reader starts — leaving
`streamingResponse.value` empty. `isAuthError('')` returns false, so the
code falls to the generic message.
## Test plan
- [ ] User without AI access sees "Unauthorized Access" when using
NL-to-SQL generation
- [ ] Non-auth HTTP errors continue to show generic "Failed to generate
SQL query"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
Fixes - https://github.com/openobserve/o2-enterprise/issues/1892
- Added explicit permission check in `chat` and `chat_stream` handlers
that returns `MetaHttpResponse::forbidden("Unauthorized Access")` (HTTP
403) when the user lacks AI access, ensuring consistent 403 behavior
regardless of OpenFGA state
- Updated all 4 SSE error event handlers in `O2AIChat.vue` to recognize
authorization-related error messages and display the standard
"Unauthorized Access" message
## Why
When a user without AI permissions used the query editor, the auth
middleware could pass the request through (e.g., when OpenFGA was
disabled), and the agent service error would arrive as an SSE event
within a 200 response — bypassing the standard 403 flow. This fix
ensures the backend returns proper HTTP 403 and the frontend displays
the standard unauthorized message consistently.
## Test plan
- [ ] Verify non-root user without AI permission gets HTTP 403 from
`/api/{org}/ai/chat_stream`
- [ ] Verify non-root user without AI permission gets HTTP 403 from
`/api/{org}/ai/chat`
- [ ] Verify root users are unaffected (bypass permission check)
- [ ] Verify the frontend displays "Unauthorized Access: You are not
authorized to perform this operation..." for auth-related SSE errors
- [ ] Verify non-auth SSE errors continue to display normally
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## What changed
Added a stream-selected guard before setting `searchObj.loading = true`
in the auto-query trigger in `SearchBar.vue`, and added an explicit
`searchObj.loading = false` reset in `Index.vue`'s `runQueryFn` when no
stream is selected.
## Why
When no stream is selected, auto-query is enabled, and the user switches
the date-time mode (relative → absolute → relative), `searchObj.loading`
was set to `true` by SearchBar but never reset to `false`. The
`runQueryFn` in Index.vue would early-return due to an empty
`selectedStream`, but did not clear the loading state, leaving the UI
stuck in an infinite loading spinner.
## Approach
- **SearchBar.vue** (line 2719): Added
`searchObj.data.stream.selectedStream.length > 0` to the condition that
triggers an auto-query on date-time change, preventing
`searchObj.loading = true` when there is no stream to query.
- **Index.vue** (lines 978–981): Inside `runQueryFn`, when
`selectedStream.length` is 0, explicitly set `searchObj.loading = false`
and `searchObj.runQuery = false` before returning, as a defense-in-depth
reset.
## What changed
Enriches the ServiceGraph Node Detail Panel for **inferred services**
(uninstrumented dependencies discovered from span attributes —
databases, message queues, RPC backends, external APIs). Instead of
showing only a single operation for these services, the panel now
displays type-specific dynamic tabs (Hosts, Databases, Queries,
Destinations, URLs, RPC Services) using field fallback chains with
COALESCE expressions, filtered against the actual stream schema.
## Why
Inferred services (detected via `infer_service_name`) previously showed
only one operation in the panel because they're derived from
CLIENT/PRODUCER spans. There was no way to see the different servers,
databases, or URLs they connect to. This PR adds structured context by
inspecting peer attributes (`server_address`, `net_peer_name`,
`db_namespace`, `messaging_destination_name`, etc.) and grouping data
into meaningful tabs per service type.
## Approach
- **INFERRED_SERVICE_TABS registry** — maps each `service_type`
(database/queue/rpc/external) to its relevant tabs with fallback field
chains (e.g., `["server_address", "net_peer_name", "net_peer_ip",
"network_peer_address"]`)
- **SQL helper functions** — `buildCoalesceExpr` and `buildNullCheck`
generate COALESCE expressions for GROUP BY/SELECT and OR clauses for
WHERE filters
- **Schema-aware field filtering** — `resolveStreamSchema` fetches the
trace stream schema; each tab's fallback fields are filtered against it,
dropping tabs with zero surviving fields
- **Caller-aware operations** — Operations table gains a "Caller" column
for inferred services, showing which upstream service invokes each
operation
- **Filter construction** — Row clicks on inferred service tabs generate
`(field1 = 'val' OR field2 = 'val')` filters instead of single-field
equality
- **Metrics tab hidden** for inferred services — no meaningful metric
correlation for uninstrumented backends
- Non-breaking — existing single-field `resourceFilter` paths left
unchanged; `handleServicesCatalogViewTraces` normalizes legacy string
payloads
## What changed
- Extracted parser-limitation guards into `isParserLimitation()` and
refactored `useSearchQuery.ts` to **not block the search** when the PEG
parser flags a false-positive error — the squiggle still shows but the
query proceeds to the backend
- Added a 4th parser-limitation guard for `found="("` inside window
function clauses (covers `SUM(COUNT(*)) OVER (...)`, `NTILE(N) OVER
(PARTITION BY COALESCE(...)...)`)
- Unified all four `fetchRumEventsForTrace` queries to a consistent
±60,000,000 µs buffer around the trace window
- Added `allowedTracingUrls` config + `propagatorTypes` to the RUM SDK
config snippet in `FrontendRumConfig.vue`
- Added an "Explain" query dropdown menu item in the logs SearchBar
- Fixed various traces/RUM UI padding and border issues
(PlayerEventsSidebar, TraceDetails, TracesMetricsDashboard,
SessionViewer)
- Updated i18n keys across all 12 language files
## Why
Two valid SQL queries produced false-positive validation errors
(`SUM(COUNT(*)) OVER (...)`, `NTILE(...) OVER (PARTITION BY
COALESCE(...)...)`), and even after suppressing those, the query was
still **blocked from executing** by `useSearchQuery.ts`. The PEG parser
has known gaps that the DataFusion backend handles correctly — there was
no reason to block valid queries. The RUM event queries also had
inconsistent time buffers across different calls (some used ±10s, others
±60s), causing missed RUM events in trace correlation.
## Approach
- Extracted the 4 parser-limitation guards from `validateSql()` into a
pure sync `isParserLimitation()` function exported from
`sqlDiagnostics.ts`, then reused it in `useSearchQuery.ts` to let query
execution continue when only parser limitations are detected
- Replaced inline constants with a shared `RUM_TIME_BUFFER_US =
60_000_000` in `useRumSpanBuilder.ts` so all four
`fetchRumEventsForTrace` queries use the same ±60s window
- Added `{ timeout: 30000 }` to the aggregate mutation test that was
timing out on ~5,500 broken queries
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
## Summary
- **Cloud visibility**: `list()` in `src/service/db/alerts/templates.rs`
was gating `DEFAULT_ORG` templates behind `!cfg!(feature = \"cloud\")`,
so any non-default org on cloud received an empty template list. Removed
the flag entirely — all `DEFAULT_ORG` templates (prebuilt and custom)
are now visible to every org, matching the existing ENT behaviour. This
is safe because cloud org identifiers are UUID-assigned; no regular user
can create an org named `default`.
- **Prebuilt templates fully read-only for non-root**: previously only
delete was blocked (403); updates were silently allowed. Now both
`save()` and `delete()` return `PrebuiltReadOnly` (403) for non-root
callers. Root users retain full access via `is_root_user()` bypass
(consistent with existing patterns in the codebase). Create with a
reserved name returns `ReservedName` (400) for everyone including root.
- **Frontend hardcoding removed**: `AddDestination.vue` was using a
`DEFAULT_TEMPLATE_SENTINEL` (`"__default__"`) and constructing prebuilt
template names client-side. Dropdown is now populated from the
`isPrebuilt: true` entries returned by `GET /alerts/templates`.
`TemplateList.vue` edit button now matches delete: shown-but-disabled
with `systemReadOnly` tooltip for prebuilt rows.
## Root cause
The `!cfg!(feature = "cloud")` flag in `list()` was added to prevent
`DEFAULT_ORG` data bleeding across cloud tenants. This correctly scopes
custom templates but also hides system prebuilt templates that every org
needs. Since prebuilt templates are now enforced as read-only
(non-root), exposing all DEFAULT_ORG templates to all orgs carries no
cross-tenant mutation risk.
## Test plan
- [ ] Non-default org on cloud: `GET /api/{org}/alerts/templates`
returns `prebuilt_slack`, `prebuilt_email`, etc. with `isPrebuilt: true`
- [ ] Non-root user: `PUT /api/{org}/alerts/templates/prebuilt_slack`
returns 403
- [ ] Non-root user: `DELETE /api/{org}/alerts/templates/prebuilt_slack`
returns 403
- [ ] Root user: `PUT /api/{org}/alerts/templates/prebuilt_slack`
succeeds
- [ ] Root user: `DELETE /api/{org}/alerts/templates/prebuilt_slack`
succeeds
- [ ] Any user: `POST /api/{org}/alerts/templates` with name
`prebuilt_slack` returns 400
- [ ] Destinations page: prebuilt destination template dropdown shows
`prebuilt_<type>` sourced from API
- [ ] `TemplateList`: edit button disabled (not hidden) for prebuilt
rows, with "System Read Only" tooltip
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
## What changed
- Added a wrap/unwrap button (toggle) in the CorrelatedLogsTable header
toolbar, matching the pattern from `logs/SearchResult.vue` (`wrap-text`
icon, `OTooltip` with `search.messageWrapContent` i18n key)
- Replaced the hard-coded 800px column `maxSize` with a dynamic cap
computed from the table container's actual width, tracked via
`ResizeObserver`
- Last-column fill: if total column widths are less than the container
and the last column is a `longTextFields` type, the remaining space is
added to it
## Summary
- **OTextarea resize**: Changed default resize behaviour from `resize-y`
to `resize-none`. `OTextarea` uses `inheritAttrs: false`, so
`style`/`class` attrs passed by consumers land on the outer wrapper
`<div>`, not the native `<textarea>`. The `resize-y` class was allowing
users to manually drag the textarea to grow it indefinitely — this
didn't exist with the previous `q-input` implementation.
- **License.vue style cleanup**: Removed `style="height: 200px"` and
`style="min-height: 150px"` from the two `OTextarea` usages. These were
being applied to the outer wrapper div (wrong element) and had no effect
on the actual textarea size. The `rows` prop already controls height
correctly.
- **Button spacing**: Added `tw:mt-3` gap between the license key
textarea and the "Update License" button.
## Test plan
- [ ] Navigate to Settings → License page with no license installed
- [ ] Verify the "Enter License Key" textarea renders at a fixed height
and cannot be resized by dragging
- [ ] Verify the "Update License" button has visible spacing below the
textarea
- [ ] Verify pasting a long license key causes scroll within the
textarea (not expansion)
- [ ] Trigger the update form (on a page with an active license) and
verify the same resize-none behaviour
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>