feat(inspect): add & chart foreign metrics another OAP wrote to shared storage (#73)
Metrics Inspect can now add metrics the connected OAP doesn't define — ones
another OAP (an older version, or a different distribution that doesn't carry
the analysis rule) wrote into the shared storage — and chart their values.
A new "Foreign metric" tab in the + add metric drawer takes the metric name,
scope, and the storage value column + type (read from the catalog of the OAP
that does define the metric). Stage several with "+ add to list" and bulk-add
them, board-cap aware, through the same counting footer as the catalog tab.
Each widget enumerates the metric's entities and plots the value series via
OAP's new POST /inspect/values (the admin-port counterpart to execExpression,
which can't evaluate a metric the OAP has no local model for). Foreign widgets
carry a FOREIGN pill, behave like any other widget (entity nav, chart-cycle,
multi-select), and persist their selection + custom entities across reloads.
Absent-endpoint and upstream errors are reported honestly: a 404 on
/inspect/values says the OAP build is too old rather than "inspect disabled",
and OAP's own {error} text (a wrong value column, a metric that is actually
defined locally, an unsupported shape) now reaches the operator instead of a
bare HTTP 4xx.Horizon UI is the next-generation web UI for Apache SkyWalking — a config-driven, dark-dense, multi-layer observability front end built for feature parity with the legacy skywalking-booster-ui on the same OAP GraphQL query-protocol and MQE. It renders services, instances, endpoints, topology, traces, logs, alarms, and profiling across 44 instrumentation layers, and ships an in-browser admin suite for runtime rules, RBAC, template management, and cluster status. Dashboards are JSON templates published to OAP — new screens are configuration, not code.
visibleWhen) — render a widget only when an MQE metric has a value or an entity attribute matches (e.g. language = JAVA, container_name = lifecycle).queryTraces on BanyanDB with inline spans, or queryBasicTraces on any backend), plus Zipkin traces in a separate tab when a layer enables both, with state/order/duration/tag filters, a duration distribution chart, and second-precision time windows..map files, resolved to original file/line/column/symbol with a source snippet.Horizon UI is a pnpm-workspaces monorepo:
apps/ui — the Vue 3 + TypeScript (strict) single-page app, built with Vite. State via Pinia, data via @tanstack/vue-query, charts via Apache ECharts (wrapped — never instantiated directly in a view), topology and flame graphs via D3, 3D via Three.js + TresJS, code editing via Monaco.apps/bff — a Fastify (Node) backend-for-frontend. It is the only tier that talks to OAP, shaping every reply for the SPA, owning timezone conversion, template sync, auth/RBAC, and the audit log.packages/api-client, packages/design-tokens, packages/templates — the shared GraphQL client, the canonical design tokens, and the bundled dashboard JSON.The BFF speaks three OAP contracts, all owned upstream and treated as fixed:
POST /graphql) — metrics, topology, traces, logs, alarms, browser errors.17128) — runtime rules/DSL, cluster status, metrics inspect, live debugging.The flow inside the BFF is one-directional — http → logic → client → OAP — and bundled templates are synced to OAP on first boot (bundled → local draft → push), after which the running UI renders only what OAP serves.
Prerequisites: Node (see package.json engines — >=22) and pnpm via Corepack (the repo pins the version through packageManager).
corepack enable
pnpm install
Run the dev servers — Vite serves the UI on :9091 and proxies /api to the BFF on :8081:
pnpm dev # run UI + BFF together pnpm dev:ui # UI only (Vite on :9091) pnpm dev:bff # BFF only (:8081, NODE_ENV=development, tsx watch)
Quality gates and build:
pnpm type-check # TypeScript strict, all packages pnpm lint pnpm test:unit # vitest + jsdom pnpm build # build all packages pnpm package # produce the distributable dist/ (BFF dist/server.js + static UI) pnpm start # run the packaged server (HORIZON_CONFIG=./horizon.yaml)
License headers and dependency licenses are enforced in CI via skywalking-eyes — run pnpm license:check (or pnpm license:fix) before pushing.
A multi-stage, multi-arch image (linux/amd64, linux/arm64) ships the BFF plus the static UI on node Alpine, runs as a non-root horizon user, exposes port 8081, and mounts /data for state files. The Dockerfile builds dist/ from source inside the image (no host pre-step); images are published to GHCR and Docker Hub per release.
docker build -t horizon-ui:local . docker run --rm -p 8081:8081 -v "$PWD/horizon.yaml:/app/horizon.yaml:ro" horizon-ui:local
See docs/setup/container-image.md for image tags, env vars, mounting horizon.yaml, and a Kubernetes example.
Horizon UI is configured by a single horizon.yaml (hot-reloaded, with ${VAR} environment-variable interpolation) — see horizon.example.yaml. Key sections:
server — host / port.oap — queryUrl, adminUrl, zipkinUrl, timeoutMs, and optional outbound basic-auth.auth — backend local or ldap (with LDAP bind / user-filter / group-mapping and an optional audited break-glass local admin).rbac — four built-in roles (viewer / maintainer / operator / admin) over fine-grained, verb-namespaced permissions (e.g. dashboard:write, rule:write:structural, source-map:write).session, debugLog, sourceMaps (browser-error source-map cache), layers.excluded, query.landingServiceCap, and the state-file paths (audit log, setup state, alarms state, wire debug log) — defaulting under /data/* in the container.Local user passwords are argon2-hashed; generate a hash with the BFF CLI. Set session.cookieSecure: true when running behind HTTPS.
Operator-focused documentation (setup, OAP compatibility, access control, customization, components, and operate) lives in this repo under docs/ and is rendered on the SkyWalking website. Start with the Quick Start, then OAP Connection and Auth/RBAC.
Contributions are welcome. Horizon UI is a greenfield rewrite that tracks the OAP GraphQL query-protocol and MQE — backend contracts are fixed and owned by apache/skywalking. Read CLAUDE.md for the project's working principles (correctness first, validate against a live OAP, TypeScript strict, charts wrapped, density beats whitespace), keep CHANGELOG.md current, and run the type-check / lint / unit-test / license-header gates before opening a PR.
Licensed under the Apache License 2.0 — see LICENSE and NOTICE.
Apache SkyWalking, SkyWalking, and the Apache feather logo are trademarks of The Apache Software Foundation.