You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/05/24 00:25:42 UTC
(skywalking-horizon-ui) branch fix/release-finalize-docker-context created (now 0c236da)
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a change to branch fix/release-finalize-docker-context
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
at 0c236da fix(release): correct docker buildx context path in release-finalize.sh
This branch includes the following new commits:
new 0c236da fix(release): correct docker buildx context path in release-finalize.sh
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(skywalking-horizon-ui) 01/01: fix(release): correct docker buildx context path in release-finalize.sh
Posted by wu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch fix/release-finalize-docker-context
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
commit 0c236da9408aeff69fbcee8404839b6d578c643f
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Sun May 24 08:25:25 2026 +0800
fix(release): correct docker buildx context path in release-finalize.sh
`git archive --prefix=src/ <tag> | tar -x` (run inside ${WORK_DIR})
extracts the checkout into ${WORK_DIR}/src, so BUILD_SRC=${WORK_DIR}/src
IS the build context — the trailing `/src` on the buildx invocation
made it look for ${WORK_DIR}/src/src, which doesn't exist, and the
post-vote image push 0.5.0 failed at Step 5 with:
ERROR: failed to build: unable to prepare context: path
".../.finalize-work/src/src" not found.
Drop the stray segment and add a doc-comment noting the layout so the
next maintainer doesn't re-introduce it. Other steps (SVN promote,
GitHub release) are unaffected.
---
.../apache-skywalking-horizon-ui-0.5.0-bin.tar.gz | Bin 0 -> 13135031 bytes
...ache-skywalking-horizon-ui-0.5.0-bin.tar.gz.asc | 17 +
...e-skywalking-horizon-ui-0.5.0-bin.tar.gz.sha512 | 1 +
.../apache-skywalking-horizon-ui-0.5.0-src.tar.gz | Bin 0 -> 4329306 bytes
...ache-skywalking-horizon-ui-0.5.0-src.tar.gz.asc | 17 +
...e-skywalking-horizon-ui-0.5.0-src.tar.gz.sha512 | 1 +
scripts/.finalize-work/release-notes.md | 117 +
scripts/.finalize-work/src/.asf.yaml | 43 +
.../src/.claude/skills/build-dashboard/SKILL.md | 119 +
.../src/.claude/skills/local-boot/SKILL.md | 164 +
.../.claude/skills/local-boot/horizon.demo.yaml | 114 +
.../.claude/skills/local-boot/horizon.ldap.yaml | 125 +
.../.claude/skills/local-boot/horizon.local.yaml | 111 +
.../src/.claude/skills/local-boot/ldap-seed.ldif | 50 +
scripts/.finalize-work/src/.dockerignore | 34 +
.../.finalize-work/src/.github/workflows/ci.yaml | 181 +
.../src/.github/workflows/publish-image.yaml | 200 +
scripts/.finalize-work/src/.gitignore | 43 +
scripts/.finalize-work/src/.licenserc.yaml | 88 +
scripts/.finalize-work/src/CHANGELOG.md | 345 +
scripts/.finalize-work/src/CLAUDE.md | 100 +
scripts/.finalize-work/src/Dockerfile | 80 +
scripts/.finalize-work/src/HEADER | 14 +
scripts/.finalize-work/src/LICENSE | 233 +
scripts/.finalize-work/src/NOTICE | 5 +
scripts/.finalize-work/src/README.md | 58 +
.../.finalize-work/src/apps/bff/eslint.config.mjs | 41 +
scripts/.finalize-work/src/apps/bff/package.json | 45 +
.../src/apps/bff/src/audit/logger.ts | 66 +
.../src/bundled_templates/alert/page-setup.json | 5 +
.../bff/src/bundled_templates/layers/activemq.json | 419 ++
.../layers/alipay_mini_program.json | 250 +
.../bff/src/bundled_templates/layers/apisix.json | 300 +
.../src/bundled_templates/layers/aws_dynamodb.json | 420 ++
.../bff/src/bundled_templates/layers/aws_eks.json | 339 +
.../src/bundled_templates/layers/aws_gateway.json | 271 +
.../bff/src/bundled_templates/layers/aws_s3.json | 188 +
.../src/bundled_templates/layers/bookkeeper.json | 287 +
.../bff/src/bundled_templates/layers/browser.json | 243 +
.../bundled_templates/layers/cilium_service.json | 487 ++
.../src/bundled_templates/layers/clickhouse.json | 475 ++
.../bundled_templates/layers/elasticsearch.json | 491 ++
.../bundled_templates/layers/envoy_ai_gateway.json | 406 ++
.../bff/src/bundled_templates/layers/flink.json | 561 ++
.../bff/src/bundled_templates/layers/general.json | 980 +++
.../apps/bff/src/bundled_templates/layers/ios.json | 343 +
.../apps/bff/src/bundled_templates/layers/k8s.json | 461 ++
.../src/bundled_templates/layers/k8s_service.json | 602 ++
.../bff/src/bundled_templates/layers/kafka.json | 363 +
.../bff/src/bundled_templates/layers/kong.json | 375 +
.../bff/src/bundled_templates/layers/mesh.json | 615 ++
.../bff/src/bundled_templates/layers/mesh_cp.json | 239 +
.../bff/src/bundled_templates/layers/mesh_dp.json | 204 +
.../bff/src/bundled_templates/layers/mongodb.json | 418 ++
.../bff/src/bundled_templates/layers/mysql.json | 317 +
.../bff/src/bundled_templates/layers/nginx.json | 324 +
.../bff/src/bundled_templates/layers/os_linux.json | 218 +
.../src/bundled_templates/layers/os_windows.json | 151 +
.../src/bundled_templates/layers/postgresql.json | 379 +
.../bff/src/bundled_templates/layers/pulsar.json | 314 +
.../bff/src/bundled_templates/layers/rabbitmq.json | 363 +
.../bff/src/bundled_templates/layers/redis.json | 335 +
.../bff/src/bundled_templates/layers/rocketmq.json | 373 +
.../bundled_templates/layers/so11y_go_agent.json | 108 +
.../bundled_templates/layers/so11y_java_agent.json | 108 +
.../src/bundled_templates/layers/so11y_oap.json | 387 +
.../bundled_templates/layers/so11y_satellite.json | 117 +
.../bundled_templates/layers/virtual_cache.json | 252 +
.../bundled_templates/layers/virtual_database.json | 155 +
.../bundled_templates/layers/virtual_genai.json | 290 +
.../src/bundled_templates/layers/virtual_mq.json | 241 +
.../layers/wechat_mini_program.json | 317 +
.../bff/src/bundled_templates/overviews/mesh.json | 124 +
.../src/bundled_templates/overviews/services.json | 172 +
.../bff/src/bundled_templates/theme/active.json | 3 +
.../bundled_templates/time-defaults/global.json | 3 +
.../.finalize-work/src/apps/bff/src/cli/hash.ts | 44 +
.../src/apps/bff/src/client/cluster.ts | 93 +
.../src/apps/bff/src/client/config-dump.ts | 60 +
.../src/apps/bff/src/client/graphql.ts | 134 +
.../src/apps/bff/src/client/index.ts | 130 +
.../src/apps/bff/src/client/zipkin.ts | 166 +
.../src/apps/bff/src/config/loader.test.ts | 150 +
.../src/apps/bff/src/config/loader.ts | 178 +
.../src/apps/bff/src/config/schema.ts | 320 +
scripts/.finalize-work/src/apps/bff/src/errors.ts | 42 +
.../src/apps/bff/src/http/admin/alarm-rules.ts | 272 +
.../src/apps/bff/src/http/admin/auth-status.ts | 205 +
.../src/apps/bff/src/http/admin/cluster.ts | 52 +
.../src/apps/bff/src/http/admin/dsl/_shared.ts | 172 +
.../src/apps/bff/src/http/admin/dsl/catalog.ts | 74 +
.../src/apps/bff/src/http/admin/dsl/dump.ts | 71 +
.../src/apps/bff/src/http/admin/dsl/oal.ts | 101 +
.../src/apps/bff/src/http/admin/dsl/rule.ts | 166 +
.../src/apps/bff/src/http/admin/inspect.ts | 407 ++
.../src/apps/bff/src/http/admin/live-debug.ts | 342 +
.../src/apps/bff/src/http/admin/oap-config.ts | 75 +
.../apps/bff/src/http/admin/overview-templates.ts | 199 +
.../src/apps/bff/src/http/admin/template-sync.ts | 344 +
.../src/apps/bff/src/http/admin/users.ts | 134 +
.../src/apps/bff/src/http/auth-health.ts | 121 +
.../src/apps/bff/src/http/config/alarms.ts | 125 +
.../src/apps/bff/src/http/config/bundle.ts | 217 +
.../src/apps/bff/src/http/config/dashboard.ts | 61 +
.../src/apps/bff/src/http/config/layer-template.ts | 59 +
.../src/apps/bff/src/http/config/overview.ts | 92 +
.../src/apps/bff/src/http/config/setup.ts | 138 +
.../src/apps/bff/src/http/query/alarms.ts | 597 ++
.../src/apps/bff/src/http/query/async-profile.ts | 409 ++
.../src/apps/bff/src/http/query/dashboard.test.ts | 396 ++
.../src/apps/bff/src/http/query/dashboard.ts | 749 ++
.../src/apps/bff/src/http/query/ebpf.ts | 637 ++
.../apps/bff/src/http/query/endpoint-dependency.ts | 532 ++
.../src/apps/bff/src/http/query/endpoint.ts | 191 +
.../src/apps/bff/src/http/query/info.ts | 141 +
.../src/apps/bff/src/http/query/instance.ts | 201 +
.../src/apps/bff/src/http/query/landing.ts | 529 ++
.../src/apps/bff/src/http/query/log.ts | 342 +
.../src/apps/bff/src/http/query/menu.ts | 343 +
.../src/apps/bff/src/http/query/preflight.ts | 45 +
.../src/apps/bff/src/http/query/profile.ts | 313 +
.../src/apps/bff/src/http/query/topology.ts | 585 ++
.../src/apps/bff/src/http/query/trace-tag.ts | 131 +
.../src/apps/bff/src/http/query/trace.ts | 476 ++
.../src/apps/bff/src/http/query/ttl.ts | 81 +
.../src/apps/bff/src/http/query/zipkin.ts | 316 +
.../.finalize-work/src/apps/bff/src/http/user.ts | 192 +
scripts/.finalize-work/src/apps/bff/src/logger.ts | 58 +
.../src/apps/bff/src/logic/alarms/bundled.ts | 77 +
.../apps/bff/src/logic/alarms/service-layer-map.ts | 128 +
.../src/apps/bff/src/logic/alarms/store.ts | 189 +
.../src/apps/bff/src/logic/dashboard/defaults.ts | 196 +
.../src/apps/bff/src/logic/inspect/attribution.ts | 279 +
.../src/apps/bff/src/logic/inspect/exec.ts | 208 +
.../src/apps/bff/src/logic/inspect/parser-mal.ts | 92 +
.../src/apps/bff/src/logic/inspect/parser-oal.ts | 56 +
.../src/apps/bff/src/logic/layers/loader.test.ts | 131 +
.../src/apps/bff/src/logic/layers/loader.ts | 694 ++
.../src/apps/bff/src/logic/oap/capabilities.ts | 102 +
.../src/apps/bff/src/logic/overview/loader.ts | 268 +
.../src/apps/bff/src/logic/preflight/preflight.ts | 171 +
.../src/apps/bff/src/logic/setup/store.ts | 92 +
.../src/apps/bff/src/logic/templates/aggregator.ts | 65 +
.../src/logic/templates/global-defaults-bundled.ts | 89 +
.../src/apps/bff/src/logic/templates/names.ts | 147 +
.../src/apps/bff/src/logic/templates/sync.ts | 376 +
.../.finalize-work/src/apps/bff/src/rbac/policy.ts | 34 +
.../src/apps/bff/src/rbac/route-policy.ts | 296 +
.../src/apps/bff/src/rbac/verbs.test.ts | 87 +
.../.finalize-work/src/apps/bff/src/rbac/verbs.ts | 104 +
scripts/.finalize-work/src/apps/bff/src/server.ts | 282 +
.../src/apps/bff/src/user/break-glass.test.ts | 52 +
.../src/apps/bff/src/user/break-glass.ts | 50 +
.../src/apps/bff/src/user/ldap-health.ts | 76 +
.../src/apps/bff/src/user/ldap.test.ts | 107 +
.../.finalize-work/src/apps/bff/src/user/ldap.ts | 330 +
.../.finalize-work/src/apps/bff/src/user/local.ts | 46 +
.../src/apps/bff/src/user/middleware.ts | 81 +
.../src/apps/bff/src/user/seen-cache.ts | 67 +
.../src/apps/bff/src/user/sessions.ts | 95 +
.../src/apps/bff/src/util/dns-fanout.ts | 91 +
.../src/apps/bff/src/util/mqe-catalog.ts | 177 +
.../src/apps/bff/src/util/mqe-target.ts | 215 +
.../.finalize-work/src/apps/bff/src/util/time.ts | 196 +
.../src/apps/bff/src/util/trace-protocol-cache.ts | 92 +
scripts/.finalize-work/src/apps/bff/tsconfig.json | 14 +
scripts/.finalize-work/src/apps/ui/env.d.ts | 23 +
.../.finalize-work/src/apps/ui/eslint.config.mjs | Bin 0 -> 2037 bytes
scripts/.finalize-work/src/apps/ui/index.html | 36 +
scripts/.finalize-work/src/apps/ui/package.json | 56 +
.../.finalize-work/src/apps/ui/public/favicon.ico | Bin 0 -> 1397 bytes
.../.finalize-work/src/apps/ui/public/favicon.svg | 29 +
scripts/.finalize-work/src/apps/ui/src/App.vue | 23 +
.../.finalize-work/src/apps/ui/src/api/client.ts | 688 ++
.../src/apps/ui/src/api/scopes/admin-auth.ts | 120 +
.../src/apps/ui/src/api/scopes/admin-users.ts | 53 +
.../src/apps/ui/src/api/scopes/alarms.test.ts | 127 +
.../src/apps/ui/src/api/scopes/alarms.ts | 90 +
.../src/apps/ui/src/api/scopes/async-profile.ts | 60 +
.../src/apps/ui/src/api/scopes/configs.ts | 120 +
.../src/apps/ui/src/api/scopes/dsl.ts | 190 +
.../src/apps/ui/src/api/scopes/ebpf.ts | 58 +
.../src/apps/ui/src/api/scopes/inspect.ts | 73 +
.../src/apps/ui/src/api/scopes/layer-template.ts | 34 +
.../src/apps/ui/src/api/scopes/layer.test.ts | 198 +
.../src/apps/ui/src/api/scopes/layer.ts | 205 +
.../src/apps/ui/src/api/scopes/live-debug.ts | 64 +
.../src/apps/ui/src/api/scopes/log.ts | 63 +
.../src/apps/ui/src/api/scopes/menu.ts | 35 +
.../src/apps/ui/src/api/scopes/network-profile.ts | 103 +
.../src/apps/ui/src/api/scopes/oap-ops.ts | 33 +
.../src/apps/ui/src/api/scopes/overview.ts | 82 +
.../src/apps/ui/src/api/scopes/pprof.ts | 59 +
.../src/apps/ui/src/api/scopes/profile.ts | 77 +
.../src/apps/ui/src/api/scopes/session.ts | 33 +
.../src/apps/ui/src/api/scopes/setup.ts | 31 +
.../src/apps/ui/src/api/scopes/template-sync.ts | 120 +
.../src/apps/ui/src/api/scopes/trace.ts | 82 +
.../src/apps/ui/src/api/scopes/zipkin.ts | 72 +
.../src/apps/ui/src/assets/icons/logo-light.svg | 138 +
.../src/apps/ui/src/assets/icons/logo-sw.svg | 32 +
.../src/apps/ui/src/assets/icons/logo.svg | 29 +
.../src/apps/ui/src/assets/login-bg.jpg | Bin 0 -> 2812588 bytes
.../src/apps/ui/src/assets/styles/global.css | 39 +
.../apps/ui/src/assets/topology-icons/ACTIVEMQ.png | Bin 0 -> 8689 bytes
.../apps/ui/src/assets/topology-icons/AIOHTTP.png | Bin 0 -> 4170 bytes
.../ui/src/assets/topology-icons/APACHEKYLIN.png | Bin 0 -> 5003 bytes
.../ui/src/assets/topology-icons/APACHESHENYU.png | Bin 0 -> 2127 bytes
.../apps/ui/src/assets/topology-icons/APISIX.png | Bin 0 -> 14603 bytes
.../apps/ui/src/assets/topology-icons/ARMERIA.png | Bin 0 -> 3587 bytes
.../src/apps/ui/src/assets/topology-icons/AVRO.png | Bin 0 -> 3570 bytes
.../src/assets/topology-icons/AZUREFUNCTIONS.png | Bin 0 -> 9153 bytes
.../apps/ui/src/assets/topology-icons/BOTTLE.png | Bin 0 -> 6820 bytes
.../ui/src/assets/topology-icons/CASSANDRA.png | Bin 0 -> 5912 bytes
.../apps/ui/src/assets/topology-icons/CELERY.jpg | Bin 0 -> 6313 bytes
.../ui/src/assets/topology-icons/CLICKHOUSE.png | Bin 0 -> 158 bytes
.../src/apps/ui/src/assets/topology-icons/CUBE.png | Bin 0 -> 1722 bytes
.../ui/src/assets/topology-icons/CUBEERROR.png | Bin 0 -> 2071 bytes
.../apps/ui/src/assets/topology-icons/DATABASE.png | Bin 0 -> 1981 bytes
.../apps/ui/src/assets/topology-icons/DJANGO.png | Bin 0 -> 4595 bytes
.../src/assets/topology-icons/DOLPHINSCHEDULER.png | Bin 0 -> 7084 bytes
.../apps/ui/src/assets/topology-icons/DUBBO.png | Bin 0 -> 1715 bytes
.../ui/src/assets/topology-icons/ELASTICSEARCH.png | Bin 0 -> 2058 bytes
.../ui/src/assets/topology-icons/ENVOYPROXY.png | Bin 0 -> 5432 bytes
.../ui/src/assets/topology-icons/EVENTMESH.png | Bin 0 -> 9817 bytes
.../apps/ui/src/assets/topology-icons/EXPRESS.png | Bin 0 -> 17838 bytes
.../apps/ui/src/assets/topology-icons/FALCON.png | Bin 0 -> 6046 bytes
.../apps/ui/src/assets/topology-icons/FASTAPI.png | Bin 0 -> 17680 bytes
.../src/assets/topology-icons/FEIGNDEFAULTHTTP.png | Bin 0 -> 571 bytes
.../apps/ui/src/assets/topology-icons/FINAGLE.png | Bin 0 -> 3413 bytes
.../apps/ui/src/assets/topology-icons/FLASK.png | Bin 0 -> 7822 bytes
.../src/apps/ui/src/assets/topology-icons/GEAR.png | Bin 0 -> 2903 bytes
.../apps/ui/src/assets/topology-icons/GENAI.png | Bin 0 -> 1473 bytes
.../src/apps/ui/src/assets/topology-icons/GIN.png | Bin 0 -> 14308 bytes
.../ui/src/assets/topology-icons/GOHTTPCLIENT.png | Bin 0 -> 15475 bytes
.../ui/src/assets/topology-icons/GOHTTPSERVER.png | Bin 0 -> 15475 bytes
.../ui/src/assets/topology-icons/GOMICROCLIENT.png | Bin 0 -> 21617 bytes
.../ui/src/assets/topology-icons/GOMICROSERVER.png | Bin 0 -> 21617 bytes
.../apps/ui/src/assets/topology-icons/GRIZZLY.png | Bin 0 -> 13183 bytes
.../src/apps/ui/src/assets/topology-icons/GRPC.png | Bin 0 -> 1483 bytes
.../src/apps/ui/src/assets/topology-icons/H2.png | Bin 0 -> 1324 bytes
.../apps/ui/src/assets/topology-icons/HBASE.png | Bin 0 -> 5838 bytes
.../apps/ui/src/assets/topology-icons/HPROSE.png | Bin 0 -> 1041 bytes
.../src/apps/ui/src/assets/topology-icons/HTTP.png | Bin 0 -> 3495 bytes
.../ui/src/assets/topology-icons/HTTPCLIENT.png | Bin 0 -> 3495 bytes
.../apps/ui/src/assets/topology-icons/HTTPDARK.png | Bin 0 -> 1787 bytes
.../apps/ui/src/assets/topology-icons/HTTPS.png | Bin 0 -> 895 bytes
.../apps/ui/src/assets/topology-icons/HTTPX.png | Bin 0 -> 5510 bytes
.../apps/ui/src/assets/topology-icons/IMPALA.png | Bin 0 -> 5278 bytes
.../apps/ui/src/assets/topology-icons/INFLUXDB.png | Bin 0 -> 11955 bytes
.../apps/ui/src/assets/topology-icons/JERSEY.png | Bin 0 -> 7539 bytes
.../apps/ui/src/assets/topology-icons/JETTY.png | Bin 0 -> 1069 bytes
.../ui/src/assets/topology-icons/JETTYSERVER.png | Bin 0 -> 1573 bytes
.../apps/ui/src/assets/topology-icons/KAFKA.png | Bin 0 -> 2407 bytes
.../ui/src/assets/topology-icons/KAFKACONSUMER.png | Bin 0 -> 2407 bytes
.../src/apps/ui/src/assets/topology-icons/KONG.png | Bin 0 -> 1024 bytes
.../apps/ui/src/assets/topology-icons/KRATOS.png | Bin 0 -> 9129 bytes
.../apps/ui/src/assets/topology-icons/LIGHT4J.png | Bin 0 -> 3926 bytes
.../apps/ui/src/assets/topology-icons/LOCAL.png | Bin 0 -> 2808 bytes
.../apps/ui/src/assets/topology-icons/MARIADB.png | Bin 0 -> 9849 bytes
.../ui/src/assets/topology-icons/MICROMETER.png | Bin 0 -> 8343 bytes
.../ui/src/assets/topology-icons/MICRONAUT.png | Bin 0 -> 3137 bytes
.../apps/ui/src/assets/topology-icons/MONGODB.png | Bin 0 -> 1751 bytes
.../apps/ui/src/assets/topology-icons/MONGOOSE.png | Bin 0 -> 6180 bytes
.../src/apps/ui/src/assets/topology-icons/MOSN.png | Bin 0 -> 3990 bytes
.../apps/ui/src/assets/topology-icons/MOTAN.png | Bin 0 -> 2609 bytes
.../apps/ui/src/assets/topology-icons/MYSQL.png | Bin 0 -> 600 bytes
.../src/apps/ui/src/assets/topology-icons/NATS.png | Bin 0 -> 626 bytes
.../ui/src/assets/topology-icons/NETTYHTTP.png | Bin 0 -> 4395 bytes
.../apps/ui/src/assets/topology-icons/NGINX.png | Bin 0 -> 4893 bytes
.../apps/ui/src/assets/topology-icons/OKHTTP.png | Bin 0 -> 695 bytes
.../ui/src/assets/topology-icons/OPENFUNCTION.png | Bin 0 -> 11716 bytes
.../apps/ui/src/assets/topology-icons/ORACLE.png | Bin 0 -> 1857 bytes
.../src/apps/ui/src/assets/topology-icons/PHP.png | Bin 0 -> 9471 bytes
.../src/apps/ui/src/assets/topology-icons/PLAY.png | Bin 0 -> 2228 bytes
.../ui/src/assets/topology-icons/POSTGRESQL.png | Bin 0 -> 2430 bytes
.../apps/ui/src/assets/topology-icons/PULSAR.png | Bin 0 -> 2409 bytes
.../apps/ui/src/assets/topology-icons/PYRAMID.png | Bin 0 -> 14140 bytes
.../apps/ui/src/assets/topology-icons/PYTHON.png | Bin 0 -> 16406 bytes
.../apps/ui/src/assets/topology-icons/RABBITMQ.png | Bin 0 -> 427 bytes
.../apps/ui/src/assets/topology-icons/REDIS.png | Bin 0 -> 2025 bytes
.../apps/ui/src/assets/topology-icons/REQUESTS.png | Bin 0 -> 1609 bytes
.../apps/ui/src/assets/topology-icons/RESIN.png | Bin 0 -> 2369 bytes
.../apps/ui/src/assets/topology-icons/RESTEASY.png | Bin 0 -> 2038 bytes
.../apps/ui/src/assets/topology-icons/ROCKETMQ.png | Bin 0 -> 1670 bytes
.../apps/ui/src/assets/topology-icons/SANIC.png | Bin 0 -> 13600 bytes
.../apps/ui/src/assets/topology-icons/SEATA.png | Bin 0 -> 3692 bytes
.../ui/src/assets/topology-icons/SERVICECOMB.png | Bin 0 -> 6220 bytes
.../assets/topology-icons/SERVICECOMBMESHER.png | Bin 0 -> 6724 bytes
.../topology-icons/SERVICECOMBSERVICECENTER.png | Bin 0 -> 6926 bytes
.../apps/ui/src/assets/topology-icons/SOFARPC.png | Bin 0 -> 2371 bytes
.../apps/ui/src/assets/topology-icons/SOLONMVC.png | Bin 0 -> 3134 bytes
.../src/apps/ui/src/assets/topology-icons/SOLR.png | Bin 0 -> 1191 bytes
.../ui/src/assets/topology-icons/SPRINGMVC.png | Bin 0 -> 1685 bytes
.../ui/src/assets/topology-icons/SPRINGWEBFLUX.png | Bin 0 -> 1685 bytes
.../apps/ui/src/assets/topology-icons/STRUTS2.png | Bin 0 -> 1562 bytes
.../apps/ui/src/assets/topology-icons/TOMCAT.png | Bin 0 -> 1837 bytes
.../apps/ui/src/assets/topology-icons/TORNADO.png | Bin 0 -> 9784 bytes
.../ui/src/assets/topology-icons/UNDEFINED.png | Bin 0 -> 1094 bytes
.../apps/ui/src/assets/topology-icons/UNKNOWN.png | Bin 0 -> 1522 bytes
.../ui/src/assets/topology-icons/UNKNOWN_CLOUD.png | Bin 0 -> 2122 bytes
.../apps/ui/src/assets/topology-icons/URLLIB3.png | Bin 0 -> 702 bytes
.../src/apps/ui/src/assets/topology-icons/USER.png | Bin 0 -> 1273 bytes
.../apps/ui/src/assets/topology-icons/VERTX.png | Bin 0 -> 12031 bytes
.../ui/src/assets/topology-icons/WEBSOCKETS.png | Bin 0 -> 12189 bytes
.../ui/src/assets/topology-icons/ZOOKEEPER.png | Bin 0 -> 4847 bytes
.../ui/src/components/charts/AlarmsTimeline.vue | 385 +
.../apps/ui/src/components/charts/Sparkline.vue | 281 +
.../apps/ui/src/components/charts/TimeChart.vue | 365 +
.../src/apps/ui/src/components/charts/TopList.vue | 431 ++
.../src/apps/ui/src/components/icons/Icon.vue | 218 +
.../src/apps/ui/src/components/primitives/Btn.vue | 101 +
.../ui/src/components/primitives/CatalogNav.vue | 232 +
.../src/apps/ui/src/components/primitives/Pill.vue | 68 +
.../apps/ui/src/components/primitives/Section.vue | 80 +
.../ui/src/components/primitives/StatusDot.vue | 57 +
.../src/apps/ui/src/controls/autoRefresh.ts | 139 +
.../src/apps/ui/src/controls/clientId.ts | 70 +
.../src/apps/ui/src/controls/configBundle.ts | 198 +
.../src/apps/ui/src/controls/debugPanel.ts | 67 +
.../src/apps/ui/src/controls/eventLog.ts | 117 +
.../src/apps/ui/src/controls/templatePreference.ts | 70 +
.../src/apps/ui/src/controls/timeRange.ts | 206 +
.../ui/src/controls/useAutoRefreshSubscribe.ts | 36 +
.../src/apps/ui/src/controls/useClickTracking.ts | 92 +
.../src/features/admin/_shared/SyncAllButton.vue | 191 +
.../features/admin/_shared/SyncStatusBanner.vue | 119 +
.../features/admin/_shared/TemplateDiffModal.vue | 288 +
.../features/admin/_shared/TemplateStatusBadge.vue | 100 +
.../src/features/admin/_shared/useTemplateSync.ts | 145 +
.../admin/alert-page/AlertPageSetupView.vue | 671 ++
.../features/admin/auth-status/AuthStatusView.vue | 661 ++
.../admin/global-defaults/GlobalDefaultsAdmin.vue | 521 ++
.../admin/global-defaults/ThemePreviewCard.vue | 381 +
.../admin/layer-templates/LayerDashboardsAdmin.vue | 3198 +++++++++
.../features/admin/layer-templates/widget-mock.ts | 188 +
.../overview-templates/OverviewTemplatesAdmin.vue | 1527 ++++
.../apps/ui/src/features/admin/roles/RolesView.vue | 705 ++
.../ui/src/features/admin/users/UsersAdminView.vue | 468 ++
.../ui/src/features/alarms/AlarmDetailPanel.vue | 482 ++
.../ui/src/features/alarms/AlarmSnapshotChart.vue | 350 +
.../src/apps/ui/src/features/alarms/AlarmsView.vue | 1574 +++++
.../src/apps/ui/src/features/auth/LoginView.vue | 569 ++
.../operate/_shared/DestructiveConfirm.vue | 122 +
.../apps/ui/src/features/operate/_shared/Modal.vue | 158 +
.../ui/src/features/operate/_shared/MonacoDiff.vue | 99 +
.../ui/src/features/operate/_shared/MonacoYaml.vue | 171 +
.../ui/src/features/operate/_shared/RuleCard.vue | 278 +
.../ui/src/features/operate/_shared/grouping.ts | 82 +
.../operate/alerting-rules/AlertingRulesView.vue | 603 ++
.../features/operate/cluster/ClusterStatusView.vue | 530 ++
.../ui/src/features/operate/config/ConfigView.vue | 312 +
.../ui/src/features/operate/config/useOapConfig.ts | 44 +
.../ui/src/features/operate/dsl/DslCatalogView.vue | 625 ++
.../ui/src/features/operate/dsl/DslDumpView.vue | 154 +
.../ui/src/features/operate/dsl/DslEditorView.vue | 544 ++
.../ui/src/features/operate/dsl/OalCatalogView.vue | 450 ++
.../ui/src/features/operate/dsl/syntaxHighlight.ts | 243 +
.../ui/src/features/operate/dsl/useRuleEditor.ts | 224 +
.../src/features/operate/inspect/InspectView.vue | 2157 ++++++
.../operate/live-debug/DebugHistoryView.vue | 531 ++
.../src/features/operate/live-debug/DebugLal.vue | 1666 +++++
.../src/features/operate/live-debug/DebugMal.vue | 1522 ++++
.../src/features/operate/live-debug/DebugOal.vue | 990 +++
.../src/features/operate/live-debug/DebugView.vue | 324 +
.../operate/live-debug/LiveDebuggerView.vue | 144 +
.../features/operate/live-debug/NodeCoverage.vue | 234 +
.../src/features/operate/live-debug/constants.ts | 37 +
.../src/features/operate/live-debug/oalEntityId.ts | 207 +
.../ui/src/features/operate/live-debug/payload.ts | 136 +
.../features/operate/live-debug/useDebugHistory.ts | 198 +
.../features/operate/live-debug/useDebugSession.ts | 313 +
.../apps/ui/src/features/operate/ttl/TtlView.vue | 286 +
.../src/apps/ui/src/features/operate/ttl/useTtl.ts | 47 +
.../src/apps/ui/src/layer/LayerServiceSelector.vue | 313 +
.../src/apps/ui/src/layer/LayerShell.vue | 804 +++
.../LayerEndpointDependencyView.vue | 1662 +++++
.../useLayerEndpointDependency.ts | 58 +
.../layer/profiling/LayerAsyncProfilingView.vue | 683 ++
.../src/layer/profiling/LayerEBPFProfilingView.vue | 1384 ++++
.../layer/profiling/LayerNetworkProfilingView.vue | 985 +++
.../layer/profiling/LayerPprofProfilingView.vue | 657 ++
.../layer/profiling/LayerTraceProfilingView.vue | 1013 +++
.../src/layer/profiling/ProcessTopologyGraph.vue | 525 ++
.../ui/src/layer/profiling/ProfileFlameGraph.vue | 415 ++
.../ui/src/layer/profiling/ProfileStackRow.vue | 135 +
.../ui/src/layer/profiling/ProfileStackTable.vue | 220 +
.../apps/ui/src/layer/profiling/useNewTaskPoll.ts | 71 +
.../src/layer/service-map/LayerServiceMapView.vue | 2762 ++++++++
.../ui/src/layer/service-map/useLayerTopology.ts | 52 +
.../ui/src/layer/service-map/useTopologyIcons.ts | 107 +
.../apps/ui/src/layer/traces/LayerTracesEntry.vue | 74 +
.../apps/ui/src/layer/traces/LayerTracesView.vue | 2709 +++++++
.../ui/src/layer/traces/LayerZipkinTracesView.vue | 1313 ++++
.../ui/src/layer/traces/NativeTraceWaterfall.vue | 410 ++
.../src/apps/ui/src/layer/traces/TracePopout.vue | 839 +++
.../apps/ui/src/layer/traces/ZipkinTracePopout.vue | 499 ++
.../src/apps/ui/src/layer/traces/useLayerTraces.ts | 131 +
.../src/apps/ui/src/layer/traces/useTracePopout.ts | 66 +
.../ui/src/layer/traces/useZipkinTracePopout.ts | 50 +
.../apps/ui/src/layer/traces/useZipkinTraces.ts | 114 +
.../src/apps/ui/src/layer/useLayerEndpoints.ts | 50 +
.../src/apps/ui/src/layer/useLayerInstances.ts | 43 +
.../src/apps/ui/src/layer/useLayerLanding.ts | 102 +
.../src/apps/ui/src/layer/useSelectedEndpoint.ts | 38 +
.../src/apps/ui/src/layer/useSelectedInstance.ts | 38 +
.../src/apps/ui/src/layer/useSelectedService.ts | 39 +
scripts/.finalize-work/src/apps/ui/src/main.ts | 76 +
.../src/apps/ui/src/monaco/lal-grammar.ts | 89 +
.../src/apps/ui/src/monaco/mal-grammar.ts | 133 +
.../.finalize-work/src/apps/ui/src/monaco/setup.ts | 149 +
.../render/layer-dashboard/LayerDashboardsView.vue | 1200 ++++
.../render/layer-dashboard/useLayerDashboard.ts | 220 +
.../layer-dashboard/useLayerPageOrchestrator.ts | 345 +
.../src/render/overview/OverviewDashboardView.vue | 175 +
.../ui/src/render/overview/OverviewLanding.vue | 96 +
.../ui/src/render/overview/useOverviewDashboard.ts | 204 +
.../src/render/overview/useOverviewDashboards.ts | 92 +
.../apps/ui/src/render/widgets/AlarmsWidget.vue | 267 +
.../apps/ui/src/render/widgets/KpiTileWidget.vue | 164 +
.../src/render/widgets/MetricCompositeWidget.vue | 166 +
.../apps/ui/src/render/widgets/MetricWidget.vue | 45 +
.../apps/ui/src/render/widgets/SectionBreak.vue | 51 +
.../src/apps/ui/src/render/widgets/TableWidget.vue | 121 +
.../src/apps/ui/src/render/widgets/ValueFormat.ts | 35 +
.../src/apps/ui/src/shell/AdminFeatureWarning.vue | 225 +
.../src/apps/ui/src/shell/AppShell.vue | 173 +
.../src/apps/ui/src/shell/AppSidebar.vue | 1161 +++
.../src/apps/ui/src/shell/AppTopbar.vue | 1058 +++
.../src/apps/ui/src/shell/DebugEventPanel.vue | 217 +
.../apps/ui/src/shell/GlobalConnectivityBanner.vue | 277 +
.../src/apps/ui/src/shell/PlaceholderView.vue | 81 +
.../apps/ui/src/shell/TemplateConflictPrompt.vue | 144 +
.../src/apps/ui/src/shell/icons.test.ts | 130 +
.../.finalize-work/src/apps/ui/src/shell/icons.ts | 79 +
.../src/apps/ui/src/shell/router/index.ts | 323 +
.../src/apps/ui/src/shell/useAdminFeatures.ts | 88 +
.../src/apps/ui/src/shell/useAlarmCount.ts | 107 +
.../src/apps/ui/src/shell/useLandingOrder.ts | 45 +
.../src/apps/ui/src/shell/useLayers.test.ts | 112 +
.../src/apps/ui/src/shell/useLayers.ts | 116 +
.../src/apps/ui/src/shell/useOapInfo.ts | 113 +
.../.finalize-work/src/apps/ui/src/state/auth.ts | 88 +
.../src/apps/ui/src/state/layerSelection.ts | 128 +
.../.finalize-work/src/apps/ui/src/state/setup.ts | 409 ++
.../.finalize-work/src/apps/ui/src/state/theme.ts | 263 +
.../src/apps/ui/src/state/timeDefaults.ts | 139 +
.../src/apps/ui/src/utils/alarmEntity.ts | 98 +
.../src/apps/ui/src/utils/alarmIncidents.ts | 192 +
.../.finalize-work/src/apps/ui/src/utils/cssVar.ts | 44 +
.../.finalize-work/src/apps/ui/src/utils/debug.ts | 91 +
.../src/apps/ui/src/utils/formatters.ts | 73 +
.../src/apps/ui/src/utils/metricCatalog.ts | 535 ++
.../src/apps/ui/src/utils/metricColor.ts | 94 +
.../src/apps/ui/src/utils/serviceName.test.ts | 204 +
.../src/apps/ui/src/utils/serviceName.ts | 165 +
scripts/.finalize-work/src/apps/ui/tsconfig.json | 24 +
scripts/.finalize-work/src/apps/ui/vite.config.ts | 45 +
.../src/dist-material/release-docs/LICENSE | 474 ++
.../src/dist-material/release-docs/LICENSE.tpl | 217 +
.../src/dist-material/release-docs/NOTICE | 13 +
.../src/dist-material/release-docs/NOTICE.tpl | 12 +
scripts/.finalize-work/src/docs/README.md | 48 +
.../src/docs/access-control/admin-pages.md | 131 +
.../src/docs/access-control/audit-log.md | 145 +
.../src/docs/access-control/break-glass.md | 90 +
.../src/docs/access-control/ldap-backend.md | 115 +
.../src/docs/access-control/local-backend.md | 77 +
.../.finalize-work/src/docs/access-control/rbac.md | 212 +
.../src/docs/compatibility/cluster-status.md | 99 +
.../src/docs/compatibility/oap-version.md | 74 +
.../.finalize-work/src/docs/compatibility/ports.md | 95 +
.../src/docs/compatibility/required-modules.md | 64 +
.../.finalize-work/src/docs/components/charts.md | 60 +
.../src/docs/components/dashboard-widgets.md | 250 +
.../src/docs/components/overview-widgets.md | 248 +
.../src/docs/customization/adding-a-new-layer.md | 144 +
.../src/docs/customization/layer-templates.md | 383 +
.../src/docs/customization/menu-structure.md | 83 +
.../src/docs/customization/overview-templates.md | 263 +
scripts/.finalize-work/src/docs/design-target.md | 96 +
scripts/.finalize-work/src/docs/menu.yml | 100 +
.../src/docs/operate/cluster-metadata.md | 78 +
.../src/docs/operate/data-retention.md | 28 +
scripts/.finalize-work/src/docs/operate/inspect.md | 102 +
.../src/docs/operate/oap-configuration.md | 24 +
scripts/.finalize-work/src/docs/setup/audit.md | 24 +
scripts/.finalize-work/src/docs/setup/auth.md | 101 +
.../src/docs/setup/container-image.md | 342 +
scripts/.finalize-work/src/docs/setup/debug-log.md | 52 +
scripts/.finalize-work/src/docs/setup/files.md | 57 +
.../.finalize-work/src/docs/setup/horizon-yaml.md | 99 +
scripts/.finalize-work/src/docs/setup/oap.md | 76 +
scripts/.finalize-work/src/docs/setup/overview.md | 152 +
scripts/.finalize-work/src/docs/setup/rbac.md | 108 +
scripts/.finalize-work/src/docs/setup/server.md | 47 +
scripts/.finalize-work/src/docs/setup/session.md | 52 +
scripts/.finalize-work/src/horizon.example.yaml | 213 +
scripts/.finalize-work/src/package.json | 42 +
.../src/packages/api-client/package.json | 26 +
.../src/packages/api-client/src/alarm-status.ts | 185 +
.../src/packages/api-client/src/async-profile.ts | 167 +
.../src/packages/api-client/src/dashboard.ts | 272 +
.../src/packages/api-client/src/dsl-debugging.ts | 712 ++
.../src/packages/api-client/src/ebpf.ts | 197 +
.../src/packages/api-client/src/index.ts | 305 +
.../src/packages/api-client/src/inspect.ts | 370 +
.../src/packages/api-client/src/landing.ts | 101 +
.../src/packages/api-client/src/logs.ts | 118 +
.../src/packages/api-client/src/menu.ts | 287 +
.../src/packages/api-client/src/oal.ts | 182 +
.../src/packages/api-client/src/oap-info.ts | 76 +
.../src/packages/api-client/src/oap-ops.ts | 91 +
.../src/packages/api-client/src/overview.ts | 196 +
.../src/packages/api-client/src/preflight.ts | 55 +
.../src/packages/api-client/src/profile.ts | 166 +
.../src/packages/api-client/src/runtime-rule.ts | 244 +
.../src/packages/api-client/src/setup.ts | 163 +
.../src/packages/api-client/src/status.ts | 93 +
.../src/packages/api-client/src/topology.ts | 264 +
.../src/packages/api-client/src/trace.ts | 232 +
.../src/packages/api-client/src/types.ts | 224 +
.../src/packages/api-client/src/ui-template.ts | 138 +
.../src/packages/api-client/tsconfig.build.json | 16 +
.../src/packages/api-client/tsconfig.json | 9 +
.../src/packages/design-tokens/package.json | 30 +
.../src/packages/design-tokens/src/index.ts | 74 +
.../src/packages/design-tokens/src/themes.css | 200 +
.../src/packages/design-tokens/src/tokens.css | 338 +
.../src/packages/design-tokens/tsconfig.build.json | 16 +
.../src/packages/design-tokens/tsconfig.json | 9 +
.../src/packages/templates/package.json | 26 +
.../src/packages/templates/src/index.ts | 22 +
.../src/packages/templates/tsconfig.build.json | 16 +
.../src/packages/templates/tsconfig.json | 9 +
scripts/.finalize-work/src/pnpm-lock.yaml | 7454 ++++++++++++++++++++
scripts/.finalize-work/src/pnpm-workspace.yaml | 18 +
.../src/scripts/check-dist-licenses.mjs | 163 +
.../src/scripts/collect-dist-licenses.mjs | 388 +
scripts/.finalize-work/src/scripts/package.mjs | 130 +
.../src/scripts}/release-finalize.sh | 0
scripts/.finalize-work/src/scripts/release.sh | 589 ++
scripts/.finalize-work/src/tsconfig.base.json | 20 +
scripts/release-finalize.sh | 7 +-
544 files changed, 115638 insertions(+), 2 deletions(-)
diff --git a/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz
new file mode 100644
index 0000000..5dffaa2
Binary files /dev/null and b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz differ
diff --git a/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz.asc b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz.asc
new file mode 100644
index 0000000..040f607
--- /dev/null
+++ b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQJPBAABCAA5FiEE02CrKrILKEAycOLL6GCJONsl4GsFAmoPIjQbFIAAAAAABAAO
+bWFudTIsMi41KzEuMTIsMCwzAAoJEOhgiTjbJeBrvisP/jk30rqCukTCWfiWVde9
+RlLPg8gZcvWGT+Rvskkqnl5g8fjrm9HRoCyMlKYNQyxnj9kCDeI8MgDSFkgBd0/J
+4ePqa4ITk9T4xnpLm9NUOgDStYF0f06/0fX48UZJpJT3RFmAfuRPqJbxMek8pGlm
+vX8emhyWmHZ4eWaDXpXPgWfQ8SvEAIXmeojKQL01aCRk+047rVY3radrYMZCRuN6
+NNZeQXkNhwtp3jxG3itcvnUVbXEKXuz3kDoFjxWXMf+aWXYV1MmDPikDUOwaL2Fl
+NAQpNDfrLfjo8qd9UNGTHfnlpHQzRh2X7MA8FLyfcU+jCgTOGuDrmULIx5eifzAk
+af5was9LjR57XrJw4u77LPiqz7wbYAIEVE7MztNCZSzwbfOmbqYjsZIvVYrdxIIE
+ZZPyw6l3o1rD2RgNtrkFss9pno4aK0Yg/xGmbj1dZ8SdwtsAD47gJtPb6SsMubrg
+0Tg7XUpKUq37CCH2VLmpqaFRIu8Ybq/q8guDAl80dOW5fcLdHqY4fznGggLgjXC4
+qdkJreveRPcD66ihLwP8TDczlOu8lvG635lnZkINLZdtvmZio3VQjEFUt5o2nJQJ
+TenpmUVO1anM30Xf4EFQFPUvyiTtjbQrX5XD1Nb02QC2Yz8pPxvjS/OalVUFk1ox
+Bd80q8Qtyd3IJz9YG0h+B5ir
+=pxM+
+-----END PGP SIGNATURE-----
diff --git a/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz.sha512 b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz.sha512
new file mode 100644
index 0000000..2d6af80
--- /dev/null
+++ b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-bin.tar.gz.sha512
@@ -0,0 +1 @@
+9787e9a979350820c202196f3d5765f69d36aa1d3bdc7129f0a39606acb77643057082dd8e4cefb06e5677e8b0212ec10a4caf4208cbc70d28a9a2644f184552 apache-skywalking-horizon-ui-0.5.0-bin.tar.gz
diff --git a/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz
new file mode 100644
index 0000000..64d8054
Binary files /dev/null and b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz differ
diff --git a/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz.asc b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz.asc
new file mode 100644
index 0000000..3e66bfd
--- /dev/null
+++ b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQJPBAABCAA5FiEE02CrKrILKEAycOLL6GCJONsl4GsFAmoPIjQbFIAAAAAABAAO
+bWFudTIsMi41KzEuMTIsMCwzAAoJEOhgiTjbJeBrF5AQAKLsSrfP2uSXpaZ7twJl
+E4RNZIh6/WU76WOAyC6mEOulyv4rFh2DN179+HKAAr4yBi138M4e9ZWT9EQ+xCkw
+ZItNknCkHg3D0bi2vQbtgUsLpcTqFEFXEQG2V2p94/SDjsZk2NrhTZ5n3UL41sUG
+ZIU7by6y+2llNZZJwoIsiKj5AuHgwLvS4a93j+qVOJ7XBrQ2zUuP5vH0COQqbTCw
+rwapG5sustgiRkfiSEo6NS6Kc7OAjg6tlbys33g8PIHit4s5w27ZgWN2akX4zXkW
+irb7CUrSXJkq7+NZwucvfidyGblgC8ygOVajuZAib2AghlfYxkGrCVuB0e947/hb
+AaLxorQzxAHpvY85InOAKOekJ5bcvd3VdmNPPXbUb9W6GSlN5MVti0qmylWZsRA/
+sZ0HwI5CzNzgUrBWMEyudeiZtojumj3fo3Ta3xTHTRskhEFCerm/Oll6cfxLbwR6
+DOKA/AbF2DTRYMeennxV7TPrS2JxgdvkrjIzmMQ4lDdZzzUZUyOy5p265ac/5GSi
+VCbZjKHeB3lPlf1um+e4wCeeUzFLw+W92ybt96DUJONGQxfVR+r/xqiTHtM+C+DO
+AKX/bgu+w6g6fMCvh+3P1luBD/gf6AwNEnAT6dJ169cB3ueN0uOBpX2NPFzyVTsi
+hir4BIiqFs8BvoFv0qubIRSS
+=RFzX
+-----END PGP SIGNATURE-----
diff --git a/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz.sha512 b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz.sha512
new file mode 100644
index 0000000..1a3091b
--- /dev/null
+++ b/scripts/.finalize-work/artifacts/apache-skywalking-horizon-ui-0.5.0-src.tar.gz.sha512
@@ -0,0 +1 @@
+47c6cc1479284fd93e16930f38bb7047b5b3cdd4a26a819ed7d96c72034551618cf6663d172f193aab65a6c7cdc3f035bc008d455caa7be120db70e50fb9af26 apache-skywalking-horizon-ui-0.5.0-src.tar.gz
diff --git a/scripts/.finalize-work/release-notes.md b/scripts/.finalize-work/release-notes.md
new file mode 100644
index 0000000..2c41900
--- /dev/null
+++ b/scripts/.finalize-work/release-notes.md
@@ -0,0 +1,117 @@
+
+First Apache-style release cut from this repo: source + binary tarballs,
+GPG-signed and SHA-512 checksummed, with a self-contained binary that
+boots via `node server.js` and no `pnpm install` step. Binary distribution
+ships a regenerated `LICENSE` + `NOTICE` that enumerate every bundled
+third-party package — produced by `scripts/collect-dist-licenses.mjs`
+during packaging and validated against a deny-list before signing.
+
+### Profiling
+
+- **pprof (Go) profiling** is fully wired: pick one event per task (CPU /
+ HEAP / BLOCK / MUTEX / GOROUTINE / ALLOCS / THREADCREATE), with duration
+ shown for CPU/BLOCK/MUTEX and a sampling-rate field for BLOCK/MUTEX. Create
+ and analyze both match OAP's single-event pprof schema.
+- **eBPF profiling** gets a reworked process picker — click a row to expand
+ its full attributes, selection lives on the checkbox, anchored pop-out — a
+ refresh button on every task list, Intl-formatted times, and a hover-info
+ frame on the flame graph. Flame-graph thrash on re-analyze is gone.
+- The shared flame graph fixes "% of root" (it read a never-aggregated count),
+ highlights the selected frame across all four profilers, and shows a single
+ hover card (the library's duplicate native tooltip is suppressed).
+- After creating any profiling task (trace / async / eBPF / network / pprof)
+ the list now polls up to 4× at 10s until the new task shows up, instead of
+ leaving a stale pre-create list.
+
+### Network profiling & process topology
+
+- A booster-style honeycomb process topology: pods as hexagons, peers hugging
+ the boundary, animated protocol-coloured edges (HTTP/TCP/TLS), a node
+ pop-over, and a wide client | server edge-metric dashboard. Network task
+ creation and the task-list query now use OAP's schema field names.
+
+### Platform monitoring (operate)
+
+- Two new read-only operate pages: **Data retention** (TTL — `getRecordsTTL` /
+ `getMetricsTTL`) and **OAP configuration** (the admin-port config dump, with
+ OAP-masked secrets). Gated on new `ttl:read` / `config:read` verbs granted to
+ maintainer and above. Data retention now loads on non-BanyanDB backends too
+ (the `metadata` TTL field is optional).
+- The operate sidebar now leads with a single **Platform monitoring** group
+ (cluster status, data retention, OAP configuration) above the per-layer
+ self-observability dashboards.
+
+### Dashboards & templates
+
+- **The global time picker now drives dashboards.** Layer dashboards query
+ OAP at the picker's window and precision (MINUTE / HOUR / DAY) instead of a
+ fixed last-hour minute window, and line charts label the x-axis with real
+ times per step (e.g. `MM-DD` for a 30-day view) rather than `-Nm`.
+- **New `table` widget** for label-dimensioned metrics — pod phase per service,
+ node condition, deployment replicas, etc. — rendered as one column per label
+ (e.g. `Condition | Node`) instead of a scalar card or a misleading flat line.
+ The K8S dashboards (and kong / mongodb / elasticsearch) now use it where
+ upstream booster-ui does; widgets that were charting a single `latest(…)`
+ value as a line are now cards. The K8S Cluster view is realigned to the
+ upstream layout (totals cards · resource lines · status tables).
+- **Edit locally, publish on your terms.** Saving a dashboard/overview template
+ now writes the local bundled copy (so the edit renders immediately for
+ preview) and marks it diverged — nothing reaches OAP until you press
+ **Sync all to OAP**, which pushes only the templates that differ, behind a
+ confirmation listing exactly what will be written. A post-save tip spells out
+ that the change is local-only until published.
+- **Local-vs-remote, made explicit.** When local edits diverge from OAP, a
+ per-session prompt (by menu name, not file name) asks which to render —
+ **keep my local edits** (preview) or **use live** (which overwrites the local
+ copy with the remote version, confirmed). The layer-templates admin page
+ carries the same Local/Remote display toggle next to Sync all, and a
+ **Diverged only** filter; each diverged layer shows a yellow warning icon in
+ the sidebar.
+
+### Traces
+
+- The native trace view auto-selects OAP's trace-query API — `queryTraces`
+ (whole trace inline, BanyanDB) vs `queryBasicTraces` (segment list + a
+ per-trace fetch on click, every other backend) — and a banner states which
+ is in use; in segment-list mode the list reads "Segments" and a click loads
+ the full trace.
+- Span kind (Entry / Exit / Local) renders as a colored word, not a filled pill.
+
+### Auth, RBAC & resilience
+
+- Every OAP call — GraphQL, admin REST, and Zipkin — now carries the
+ configured basic-auth credentials, so a secured OAP no longer 401s pages.
+- The sidebar is RBAC-gated by read verb, the Roles page shows a per-role
+ menu-visibility matrix, and the Users page labels per-node "Active (24h)" /
+ "Last seen" honestly (these are tracked per BFF replica, not cluster-wide).
+- **Routes are verb-gated, not just menus.** A user without the required read
+ verb is bounced from a restricted page (e.g. a viewer can no longer reach
+ Cluster Status via the topbar OAP chip or a direct URL); the chip only links
+ there for `cluster:read`. This sits on top of the existing per-route BFF verb
+ enforcement.
+- **LDAP** resolves group membership with the service account, not the
+ logging-in user — directories that hide the group subtree from ordinary users
+ no longer collapse every login to the fallback role.
+- When OAP is unreachable the menu and admin loaders fall back to bundled
+ templates, and non-JSON OAP responses surface a clear diagnostic.
+
+### Smaller touches
+
+- Top-N widgets get hover tooltips for long names and a title-bar pop-out to
+ the full ranked list; redundant single-service name prefixes are dropped.
+- The admin template-diff modal is a wide side-by-side view with labelled
+ bundled-vs-OAP columns and an explanation of what the template drives; the
+ layer-dashboards admin rail gains an in-page search.
+- Per-layer alarm filtering uses the singular `queryAlarms` layer condition.
+- Dependency hygiene for the release: `dompurify` ≥ 3.3.2 and
+ `@fastify/static` ≥ 9.1.1 (clears the known advisories); the `general`
+ layer drops `networkProfiling`, which is instance-scoped to k8s / mesh.
+
+
+---
+
+Source & binary releases (with signatures and checksums):
+* https://dist.apache.org/repos/dist/release/skywalking/horizon-ui/0.5.0/
+* KEYS: https://dist.apache.org/repos/dist/release/skywalking/KEYS
+
+Container image: `docker pull apache/skywalking-ui:horizon-0.5.0`
diff --git a/scripts/.finalize-work/src/.asf.yaml b/scripts/.finalize-work/src/.asf.yaml
new file mode 100644
index 0000000..8ddaeba
--- /dev/null
+++ b/scripts/.finalize-work/src/.asf.yaml
@@ -0,0 +1,43 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+github:
+ description: Apache SkyWalking next-generation UI (Horizon)
+ homepage: https://skywalking.apache.org/
+ labels:
+ - skywalking
+ - observability
+ - apm
+ - ui
+ - vue
+ - dashboard
+ - tracing
+ - metrics
+ - logging
+ - topology
+ enabled_merge_buttons:
+ squash: true
+ merge: false
+ rebase: false
+ dependabot_updates: false
+ # Branch protection and PR approval are intentionally OFF during pre-1.0
+ # single-author development to keep the iteration loop tight. The explicit
+ # `~` (YAML null) tells ASF infra to clear any previously-applied
+ # `protected_branches` config — omitting the key would leave prior
+ # protections in place. Re-add status-check + review gating before v1.0
+ # or when a second committer joins.
+ protected_branches: ~
diff --git a/scripts/.finalize-work/src/.claude/skills/build-dashboard/SKILL.md b/scripts/.finalize-work/src/.claude/skills/build-dashboard/SKILL.md
new file mode 100644
index 0000000..a55d595
--- /dev/null
+++ b/scripts/.finalize-work/src/.claude/skills/build-dashboard/SKILL.md
@@ -0,0 +1,119 @@
+---
+name: build-dashboard
+description: Build a NEW Horizon UI dashboard from scratch — either a cross-layer Overview dashboard (apps/bff/src/bundled_templates/overviews/*.json) or a per-layer dashboard widget set (apps/bff/src/bundled_templates/layers/*.json). Greenfield authoring, NOT migration: design the widgets, write the MQE, validate every expression against a live OAP, then render-check in the browser.
+user-invocable: true
+---
+
+# Build a new Horizon dashboard
+
+Two dashboard families live in the BFF as static JSON, loaded at boot and served to the SPA's generic renderers. You author JSON; you do not write Vue.
+
+| Family | JSON dir | TS shape | Served by | Rendered by |
+|---|---|---|---|---|
+| **Overview** (cross-layer landing) | `apps/bff/src/bundled_templates/overviews/*.json` | `OverviewDashboard` — `packages/api-client/src/overview.ts` | `GET /api/overview/dashboards[/:id]` (`apps/bff/src/http/config/overview.ts`) | `apps/ui/src/render/overview/` + `apps/ui/src/render/widgets/` |
+| **Layer dashboard** (per-layer drill-down) | `apps/bff/src/bundled_templates/layers/*.json` | `LayerTemplate` — `apps/bff/src/logic/layers/loader.ts`; widgets are `DashboardWidget` — `packages/api-client/src/dashboard.ts` | `GET /api/admin/layer-templates`, data via `POST /api/layer/:key/dashboard` | `apps/ui/src/render/layer-dashboard/` |
+
+**Always read the TS interface for the family you're touching before writing JSON** — fields change, and the interface (plus its comments) is the contract. The existing `general.json` (layer) and `services.json` (overview) are the canonical references; copy their shape and swap the metrics, don't invent a new structure.
+
+---
+
+## The non-negotiable rule: validate MQE against a live OAP
+
+A dashboard is only "done" when every expression has been run against a real OAP and returns the shape the widget expects. Type-checks prove nothing about whether a metric exists or is at the right scope. Boot a local env first:
+
+> Use the **`local-boot`** skill to start the BFF+UI against a local OAP (or the demo). UI on `:9091`, BFF on `:8081`, login `admin`/`admin`.
+
+If no OAP is available, **stop and ask for one**. Do not guess metric names, do not fabricate wire shapes.
+
+### How to validate one expression
+
+1. **Confirm the metric exists and its catalog scope + value type** — `listMetrics`:
+ ```bash
+ curl -s -H 'Content-Type: application/json' -X POST http://localhost:12800/graphql \
+ -d '{"query":"query{listMetrics(regex:\"^service_cpm$\"){name catalog type}}"}'
+ ```
+ `catalog` is the entity scope (`SERVICE` / `SERVICE_INSTANCE` / `ENDPOINT` / `SERVICE_RELATION` / …). `type` is `REGULAR_VALUE` vs `LABELED_VALUE` — it decides whether `aggregate_labels(...)` is legal.
+2. **Run the MQE** at the scope the widget will render. The simplest path is to exercise the actual BFF route the widget uses (overview tile / layer dashboard) once the env is up, and inspect the JSON. Confirm a `card`/scalar MQE returns one number and a `line`/series MQE returns a time series.
+
+---
+
+## Entity scope is load-bearing (read this before picking metrics)
+
+Every OAP metric lives under exactly ONE entity scope and OAP does **not** auto-rollup between scopes — querying at the wrong scope returns empty, no error. (CLAUDE.md: "Metric entity-scope is load-bearing".)
+
+- A `SERVICE_INSTANCE`-scope metric (e.g. `instance_jvm_cpu`) **cannot** be a bare `line`/`card` in a SERVICE-scope dashboard. Options: aggregate to a scalar with `avg(...)`/`sum(...)` for a card/overview tile, OR show per-instance trend as a `top` widget with `top_n(...)`, OR put the bare metric under `dashboards.instance`.
+- Same one level deeper for `ENDPOINT`-scope metrics in a service dashboard.
+- Never bridge a scope mismatch with a BFF-side rollup — move the metric to the right scope or leave the slot empty.
+
+## Widget type follows MQE shape (card vs line)
+
+Verbatim from CLAUDE.md:
+> A widget whose MQE collapses to a single scalar must be `type: "card"`, not `type: "line"`. The tell-tale is the outermost call: `latest(...)`, `max(...)`, `min(...)`, `avg(<plain-metric>)`, `sum(<plain-metric>)` all reduce the window to one number. Series-shaped wrappers (`relabels(...)`, `top_n(...)`, `histogram*(...)`, `aggregate_labels(...)` without an outer scalar collapse, `rate(...)`, `increase(...)`) stay `line`. Look at the outermost function first.
+
+`LABELED_VALUE`-only: `aggregate_labels(metric, sum)` is valid only for labeled metrics; for `REGULAR_VALUE` use plain `sum()` / `avg()`. Mixing throws "result is not a labeled result".
+
+---
+
+## Building an OVERVIEW dashboard
+
+`OverviewDashboard` top-level: `id`, `title`, `description?`, `visibility?` (`'public'`|`'operate'`), `icon?`, `order?`, `layers?` (auto-hide when none of these layers report), `widgets[]`.
+
+Widget `type`s (`OverviewWidgetType`):
+- `section-break` — row header, layout only. `cols` sets the grid column count for the widgets that follow it. No data.
+- `kpi-tile` — one layer's health: `layer`, optional `showCount` (service count header), `kpis[]`. Each KPI: `{ label, mqe, unit?, aggregation?: 'sum'|'avg', style?: 'number'|'progress-bar', max? (required for progress-bar), source?: 'mqe'|'service-count' }`.
+- `metric-composite` — multi-KPI tile mixing numbers + progress bars (same `kpis[]` shape).
+- `metric` — a single scalar `mqe` (rarely needed; prefer kpi-tile).
+- `alarms` — active-alarm rail; layer-agnostic, `limit?`. Omit `layer`.
+- `topology` — embedded static service-map for a `layer`, click-through to the full map.
+- Grid: `span` (12-col) + `rowSpan`.
+
+Procedure:
+1. Decide the story the page tells (which layers, which 3-ish KPIs each). Overview tiles are a health-at-a-glance strip — prefer RPM / latency / SLA per layer.
+2. Author the JSON next to `services.json`. Group with `section-break`s.
+3. For each KPI `mqe`: validate scope+type, then confirm it reduces to a scalar (overview KPIs are scalar — the renderer shows one number or a bar). Use `aggregation` to pick sum (throughput) vs avg (everything else).
+4. Set `visibility`/`order`/`icon`/`layers`. Boot and eyeball at `http://localhost:9091`.
+
+## Building a LAYER dashboard
+
+A `LayerTemplate` (file basename must match `key` UPPER_SNAKE) carries far more than widgets — read `apps/bff/src/logic/layers/loader.ts` for the full interface. The widget sets live under `dashboards.{service,instance,endpoint,dependency,topology,trace,logs,traceProfiling,ebpfProfiling,asyncProfiling}`, each an array of `DashboardWidget`.
+
+`DashboardWidget`: `id`, `title`, `tip?`, `type` (`'card'|'line'|'top'|'record'`), `expressions[]` (MQE), `expressionLabels?` (legend / `top` tabs), `expressionUnits?`, `expressionAxes?` (0=left,1=right for dual-axis line), `unit?`, `format?` (`'int'|'decimal'|'compact'`), `span?` (default 4, 12-col), `rowSpan?` (default 8), `visibleWhen?` (hide until a metric reports — e.g. for multi-runtime instance widgets), `layerScope?`.
+
+Widget-type cheatsheet:
+- `card` — one scalar (see the card-vs-line rule). Drop a redundant outer `avg()` if the renderer already averages, unless removing it changes shape.
+- `line` — one labeled series per expression. Multi-series → set `expressionLabels` (required for the legend); dual-axis → `expressionAxes` + `expressionUnits`.
+- `top` — sorted list, usually `top_n(metric, N, des)`. Fold several rankings of the same thing into ONE `top` with multiple `expressions`+`expressionLabels`+`expressionUnits` (rendered as in-widget tabs).
+- `record` — RECORD-typed MQE (slow statements/SQL). Use `record`, not `top`.
+
+Other blocks you may need (each has its own config interface in loader.ts — read before use): `header.columns` (service-list picker columns + `orderBy`), `overview.groups` (hero tile above the picker), `topology` (node/edge metrics for the service map), `endpointDependency`, `processTopology`, `traces`, `log`, plus `components.*` feature flags that light up tabs.
+
+Procedure:
+1. Read `general.json` end-to-end as the reference, and the `LayerTemplate` interface.
+2. Decide which scopes the layer has data for (service always; instance/endpoint only if those scopes carry distinct metrics). Set `components.*`.
+3. Author widgets per scope. Apply the scope rule and the card-vs-line rule to every expression.
+4. Grid: `span` is the 12-col width; bump `rowSpan` for top-lists / percentile charts.
+5. Validate every MQE against the live OAP, then render-check each scope in the browser.
+
+---
+
+## Validate (code) then render-check (browser)
+
+```bash
+# from repo root
+pnpm --filter @skywalking-horizon-ui/bff run type-check # schema typechecks
+pnpm --filter @skywalking-horizon-ui/bff run test:unit # loaders still parse
+pnpm license:check # headers (JSON is exempt, but vue/ts edits aren't)
+```
+
+Then with the local env up (via `local-boot`):
+- Overview: open `http://localhost:9091`, find the dashboard in the sidebar (placement = `visibility`+`order`), confirm every tile shows a number (not `—`/blank).
+- Layer: navigate the layer's Service/Instance/Endpoint tabs; confirm each widget renders data and that cards are scalars, lines are series. A blank widget almost always means a scope mismatch or a metric that doesn't exist on this OAP — re-run `listMetrics`.
+
+## Pitfalls
+
+1. **Scope mismatch returns empty, not an error.** The #1 cause of a blank widget. Verify `catalog` with `listMetrics` and match it to the dashboard's scope.
+2. **Card MQE that's actually a series (or vice-versa).** Look at the outermost MQE function; pick the type from it.
+3. **`aggregate_labels` on a `REGULAR_VALUE` metric** → "result is not a labeled result". Check `type` first.
+4. **File/key mismatch.** A layer file `foo.json` must declare `"key": "FOO"`.
+5. **Inventing fields/metrics.** The OAP query-protocol and metric catalog are fixed and owned upstream. If a screen needs data the protocol doesn't expose, flag it — don't fabricate an MQE or a BFF rollup.
+6. **No `Co-Authored-By` / AI footers** on commits or PRs (project rule).
diff --git a/scripts/.finalize-work/src/.claude/skills/local-boot/SKILL.md b/scripts/.finalize-work/src/.claude/skills/local-boot/SKILL.md
new file mode 100644
index 0000000..1c16348
--- /dev/null
+++ b/scripts/.finalize-work/src/.claude/skills/local-boot/SKILL.md
@@ -0,0 +1,164 @@
+---
+name: local-boot
+description: Boot the Horizon UI dev env (BFF + UI) against a local OAP or the public Apache demo OAP, using the static configs bundled with this skill. Handles the apps/bff cwd / HORIZON_CONFIG gotcha and the demo OAP password (kept out of git via ${OAP_PASSWORD}).
+user-invocable: true
+---
+
+# Boot the Horizon UI local dev env
+
+Two bundled static configs live next to this file:
+
+- `horizon.local.yaml` — local OAP on `127.0.0.1:12800`, no OAP network auth.
+- `horizon.demo.yaml` — public Apache demo OAP, OAP password read from `${OAP_PASSWORD}`.
+
+Both define the same throwaway Horizon login users (password == username):
+`viewer`, `maintainer`, `operator`, `admin`.
+
+The stack: **BFF** (Fastify) on `:8081`, **UI** (Vite) on `:9091` proxying `/api` → `:8081`. Open **`http://127.0.0.1:9091`** (use the IPv4 literal, not `localhost` — see the proxy/IPv4 section).
+
+## The one gotcha that bites every time
+
+The BFF dev script is `tsx watch src/server.ts` and pnpm runs it with **cwd = `apps/bff`**. The config path is `process.env.HORIZON_CONFIG ?? './horizon.yaml'`, resolved relative to cwd — so a bare `./horizon.yaml` points at the non-existent `apps/bff/horizon.yaml`, and the loader silently falls back to **defaults with zero users** (every login then fails with "invalid credentials", and the boot log warns `auth.local.users is empty`).
+
+**Always pass `HORIZON_CONFIG` as an ABSOLUTE path.** Use `"$REPO/.claude/skills/local-boot/<file>"`.
+
+## Proxy + IPv4 (the second gotcha)
+
+Two things make the UI look "not accessible" even when Vite is running:
+
+- **Vite binds IPv6 `[::1]` by default**, so `127.0.0.1:9091` (IPv4) has nothing and many browsers / curl fail. **Force IPv4** with `--host 127.0.0.1`. Note `pnpm --filter ui run dev -- --host …` does NOT forward the flag — run the Vite binary directly (`apps/ui/node_modules/.bin/vite --host 127.0.0.1`).
+- **A local proxy** (ClashX / v2ray etc. — `http_proxy` / `https_proxy` / `all_proxy` pointing at `127.0.0.1:<port>`) intercepts loopback and returns `502`. Detect and bypass it for the dev hosts.
+
+Detect a local proxy before booting:
+```bash
+env | grep -iE '^(http_proxy|https_proxy|all_proxy)=' && echo "local proxy detected — will bypass loopback"
+```
+
+The browser uses its OWN proxy settings (not the shell's), so the developer must also let `127.0.0.1` / `localhost` go direct (ClashX "bypass localhost" / system proxy no-proxy list). The env-level bypass below only fixes CLI tools and Vite's own fetches.
+
+## The stale-process trap (why a config switch "didn't take")
+
+`tsx watch` keeps the old BFF alive, so a freshly launched BFF with a NEW config silently dies on `EADDRINUSE: 127.0.0.1:8081` while the OLD process keeps serving the OLD OAP. Symptom: you switch local↔demo, everything looks fine, but the UI still shows the previous OAP's data.
+
+Killing matters in two ways:
+- `pkill -f "tsx watch src/server.ts"` may miss the actual listener — also `pkill -f "tsx/dist/cli.mjs watch"`, and **verify the port is actually free with `lsof` before relaunching** (loop until free; the watcher can respawn a child).
+- After boot, **confirm which OAP the LIVE process is using** — don't trust that your new process won the port. The authoritative check:
+ ```bash
+ curl -s --noproxy '*' -b /tmp/sw.cookies http://127.0.0.1:8081/api/oap/config | grep -oE '"adminUrl":"[^"]*"'
+ # local => http://localhost:12800 ; demo => https://demo.skywalking.apache.org:17128
+ ```
+ Also grep the boot log for `EADDRINUSE` and the `configPath` line. If the adminUrl is wrong, a stale BFF is still bound — kill it, confirm `:8081` is free, relaunch.
+
+## Boot against the local OAP
+
+```bash
+REPO="$(git rev-parse --show-toplevel)"
+# 1. Kill prior dev servers AND confirm :8081 is actually free — a stale
+# BFF holding the port makes the new one die on EADDRINUSE while the
+# old config keeps serving (see "stale-process trap"):
+pkill -f "tsx watch src/server.ts" 2>/dev/null
+pkill -f "tsx/dist/cli.mjs watch" 2>/dev/null; pkill -f vite 2>/dev/null
+until ! lsof -nP -iTCP:8081 -sTCP:LISTEN >/dev/null 2>&1; do sleep 1; done
+
+# 2. BFF — absolute config path is mandatory (see gotcha above):
+HORIZON_CONFIG="$REPO/.claude/skills/local-boot/horizon.local.yaml" \
+ pnpm --filter @skywalking-horizon-ui/bff run dev &
+
+# 3. UI — IPv4 host + loopback proxy bypass (run the binary directly so
+# --host actually applies):
+( cd "$REPO/apps/ui" && \
+ env -u http_proxy -u https_proxy -u all_proxy -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY \
+ no_proxy="localhost,127.0.0.1,::1" NO_PROXY="localhost,127.0.0.1,::1" \
+ node_modules/.bin/vite --host 127.0.0.1 & )
+```
+
+Then open **`http://127.0.0.1:9091`** and log in as `admin` / `admin`.
+
+## Boot against the public demo OAP
+
+The demo OAP needs basic-auth. The password is NOT committed — ask the
+developer for it and export it before booting:
+
+```bash
+REPO="$(git rev-parse --show-toplevel)"
+read -rsp "Demo OAP password: " OAP_PASSWORD && export OAP_PASSWORD && echo
+
+pkill -f "tsx watch src/server.ts" 2>/dev/null
+pkill -f "tsx/dist/cli.mjs watch" 2>/dev/null; pkill -f vite 2>/dev/null
+until ! lsof -nP -iTCP:8081 -sTCP:LISTEN >/dev/null 2>&1; do sleep 1; done
+HORIZON_CONFIG="$REPO/.claude/skills/local-boot/horizon.demo.yaml" \
+ pnpm --filter @skywalking-horizon-ui/bff run dev &
+( cd "$REPO/apps/ui" && \
+ env -u http_proxy -u https_proxy -u all_proxy -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY \
+ no_proxy="localhost,127.0.0.1,::1" NO_PROXY="localhost,127.0.0.1,::1" \
+ node_modules/.bin/vite --host 127.0.0.1 & )
+```
+
+When invoked as an agent and `OAP_PASSWORD` is not already set, ask the
+developer for it (do not guess, do not hardcode it into a file). The OAP
+network username is fixed as `admin` in `horizon.demo.yaml`.
+
+## Boot against an LDAP directory (test)
+
+`horizon.ldap.yaml` points the BFF at a throwaway OpenLDAP seeded from
+`ldap-seed.ldif`. Stand the directory up, seed it, then boot:
+
+```bash
+REPO="$(git rev-parse --show-toplevel)"
+docker rm -f horizon-ldap 2>/dev/null
+docker run -d --name horizon-ldap -p 389:389 -p 636:636 \
+ --env LDAP_ORGANISATION="Horizon Test" --env LDAP_DOMAIN="horizon.test" \
+ --env LDAP_ADMIN_PASSWORD="admin" osixia/openldap:1.5.0
+# wait for slapd, then seed:
+until docker exec horizon-ldap ldapwhoami -x -H ldap://localhost \
+ -D "cn=admin,dc=horizon,dc=test" -w admin >/dev/null 2>&1; do sleep 1; done
+docker cp "$REPO/.claude/skills/local-boot/ldap-seed.ldif" horizon-ldap:/tmp/seed.ldif
+docker exec horizon-ldap ldapadd -x -H ldap://localhost \
+ -D "cn=admin,dc=horizon,dc=test" -w admin -f /tmp/seed.ldif
+
+pkill -f "tsx watch src/server.ts" 2>/dev/null; pkill -f "tsx/dist/cli.mjs watch" 2>/dev/null
+until ! lsof -nP -iTCP:8081 -sTCP:LISTEN >/dev/null 2>&1; do sleep 1; done
+HORIZON_CONFIG="$REPO/.claude/skills/local-boot/horizon.ldap.yaml" \
+ pnpm --filter @skywalking-horizon-ui/bff run dev &
+```
+
+Test accounts (seeded by `ldap-seed.ldif`). Login users are named after
+their role and **password == username**, mirroring `horizon.local.yaml`:
+
+| Login | Group | Role |
+|---|---|---|
+| `admin` | cn=horizon-admin | admin |
+| `operator` | cn=sre | operator |
+| `maintainer` | cn=platform | maintainer |
+| `viewer` | (none) | viewer (`*` fallback) |
+
+Directory bind account: `cn=admin,dc=horizon,dc=test` / `admin` (override via `LDAP_BIND_PW`).
+
+```bash
+# verify a login resolves the expected role:
+curl -s --noproxy '*' -H 'Content-Type: application/json' -X POST \
+ http://127.0.0.1:8081/api/auth/login -d '{"username":"admin","password":"admin"}'
+```
+
+Note: group resolution runs on the **service bind**, not the user's
+credentials (regular users usually can't read the group subtree).
+
+## Verify the BFF is healthy (no browser)
+
+```bash
+# --noproxy so a local proxy (ClashX etc.) doesn't 502 the loopback call.
+until curl -s --noproxy '*' -m2 -o /dev/null http://127.0.0.1:8081/api/auth/health; do sleep 1; done
+curl -s --noproxy '*' -c /tmp/sw.cookies -H 'Content-Type: application/json' -X POST \
+ http://127.0.0.1:8081/api/auth/login -d '{"username":"admin","password":"admin"}'
+# Expect 200 with {username, roles, verbs, landingRoute}. A 401
+# "invalid credentials" almost always means the wrong config loaded —
+# re-check the absolute HORIZON_CONFIG path and that no stale BFF holds :8081.
+```
+
+## Editing the configs
+
+These files are the source of truth for dev boot. Keep secrets out:
+local-user hashes (password == username) are fine to commit; real OAP
+passwords must stay in `${OAP_PASSWORD}` (the loader's `interpolateEnv`
+expands `${VAR}` / `${VAR:default}` in the raw YAML before parsing).
+To mint a new local-user hash: `pnpm --filter @skywalking-horizon-ui/bff cli:hash`.
diff --git a/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.demo.yaml b/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.demo.yaml
new file mode 100644
index 0000000..242ac29
--- /dev/null
+++ b/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.demo.yaml
@@ -0,0 +1,114 @@
+# Static demo-boot config for the Horizon UI BFF — points at the public
+# Apache demo OAP (demo.skywalking.apache.org). The OAP network basic-auth
+# password is NOT committed: it is read from ${OAP_PASSWORD} at boot.
+# Ask the developer for it before booting and export it, e.g.
+# read -rs OAP_PASSWORD && export OAP_PASSWORD
+# Boot via the `local-boot` skill (see SKILL.md), which passes this file
+# through HORIZON_CONFIG as an ABSOLUTE path. Horizon login users below
+# are throwaway dev credentials (password == username).
+
+server:
+ host: 127.0.0.1
+ port: 8081
+
+oap:
+ queryUrl: https://demo.skywalking.apache.org:12800
+ adminUrl: https://demo.skywalking.apache.org:17128
+ zipkinUrl: https://demo.skywalking.apache.org:9412/zipkin
+ timeoutMs: 15000
+ auth:
+ username: admin
+ # Supplied via the environment so the demo password stays out of git.
+ # interpolateEnv() in apps/bff/src/config/loader.ts expands ${OAP_PASSWORD}.
+ password: "${OAP_PASSWORD}"
+
+auth:
+ backend: local
+ local:
+ users:
+ - username: viewer
+ # password: viewer
+ passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$Gp175hqr+EF2iZ7v1fndvw$w6w9hDI59/UA+CRARChDoGRlR1TkVt6kqzApa021K+0"
+ roles: [viewer]
+ - username: maintainer
+ # password: maintainer
+ passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$w7ULwB3/jzH9FxVoHJ238A$y+qGoX6IPeOoGywLQCpfpAN5VJXcaevoWeJQhaybvQU"
+ roles: [maintainer]
+ - username: operator
+ # password: operator
+ passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$nzoI4RqiobprtzX/mJqe5Q$FY2Hi7mKep0DPHoaE++r/KD++WLUwTgRUFLde87j2Wg"
+ roles: [operator]
+ - username: admin
+ # password: admin
+ passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$joV9AVlyLS3pqq4mLrYokQ$pJLkTKrz9/LzEH6YaFljdz9k8dyBiryjwSB26Diiz9U"
+ roles: [admin]
+
+rbac:
+ enabled: true
+ roles:
+ viewer:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "overview:read"
+ maintainer:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "overview:read"
+ - "cluster:read"
+ - "inspect:read"
+ - "ttl:read"
+ - "config:read"
+ operator:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "cluster:read"
+ - "inspect:read"
+ - "ttl:read"
+ - "config:read"
+ - "overview:read"
+ - "overview:write"
+ - "setup:read"
+ - "setup:write"
+ - "dashboard:read"
+ - "dashboard:write"
+ - "alarm-setup:read"
+ - "alarm-setup:write"
+ - "alarm-rule:read"
+ - "alarm-rule:write"
+ - "rule:read"
+ - "rule:write"
+ - "rule:write:structural"
+ - "rule:delete"
+ - "rule:debug"
+ - "live-debug:read"
+ - "live-debug:write"
+ - "profile:enable"
+ admin:
+ - "*"
+ landingByRole:
+ viewer: /
+ maintainer: /operate/cluster
+ operator: /
+ admin: /
+
+session:
+ ttlMinutes: 60
+ cookieName: horizon_sid
+ cookieSecure: false
+
+audit: { file: ./horizon-audit.jsonl }
+setup: { file: ./horizon-setup.json }
+alarms: { file: ./horizon-alarms.json }
+debugLog: { enabled: false, file: ./horizon-wire.jsonl, maxBodyChars: 8192, redactAuthHeaders: true }
diff --git a/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.ldap.yaml b/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.ldap.yaml
new file mode 100644
index 0000000..6c9e0d2
--- /dev/null
+++ b/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.ldap.yaml
@@ -0,0 +1,125 @@
+# LDAP-backend boot config for the Horizon UI BFF. Authenticates against
+# an external directory; Horizon stores no passwords. This file targets
+# the throwaway test OpenLDAP described in SKILL.md ("Test the LDAP
+# backend"): base dc=horizon,dc=test, users under ou=people, groups
+# (groupOfNames) under ou=groups, seeded from ldap-seed.ldif.
+#
+# Test directory accounts (all seeded by ldap-seed.ldif). Login users
+# are named after their role and password == username, mirroring
+# horizon.local.yaml:
+# directory admin (bindDn): cn=admin,dc=horizon,dc=test / admin
+# login users (password == username):
+# admin -> cn=horizon-admin -> role admin
+# operator -> cn=sre -> role operator
+# maintainer -> cn=platform -> role maintainer
+# viewer -> (no group) -> role viewer (the "*" fallback)
+#
+# The directory bind password is read from ${LDAP_BIND_PW} (defaults to
+# the test value `admin`); for a real directory, export LDAP_BIND_PW and
+# never commit it.
+#
+# OAP still points at a local OAP so the rest of the app works. Boot via
+# the local-boot skill with an ABSOLUTE HORIZON_CONFIG path.
+
+server:
+ host: 127.0.0.1
+ port: 8081
+
+oap:
+ queryUrl: http://localhost:12800
+ adminUrl: http://localhost:12800
+ zipkinUrl: http://localhost:9412/zipkin
+ timeoutMs: 15000
+
+auth:
+ backend: ldap
+ ldap:
+ url: ldap://localhost:389
+ bindDn: "cn=admin,dc=horizon,dc=test"
+ bindPassword: "${LDAP_BIND_PW:admin}"
+ userBaseDn: "ou=people,dc=horizon,dc=test"
+ userFilter: "(uid={username})"
+ displayNameAttr: cn
+ # This test directory uses groupOfNames groups without a memberOf
+ # overlay, so resolve membership by searching ou=groups for the
+ # user's DN. Switch to `memberOf` against directories that populate it.
+ groupStrategy: search
+ groupBaseDn: "ou=groups,dc=horizon,dc=test"
+ memberAttr: member
+ timeoutMs: 5000
+ tlsInsecure: false
+ groupMappings:
+ - { group: "cn=horizon-admin,ou=groups,dc=horizon,dc=test", role: admin }
+ - { group: "cn=sre,ou=groups,dc=horizon,dc=test", role: operator }
+ - { group: "cn=platform,ou=groups,dc=horizon,dc=test", role: maintainer }
+ - { group: "*", role: viewer }
+
+rbac:
+ enabled: true
+ roles:
+ viewer:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "overview:read"
+ maintainer:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "overview:read"
+ - "cluster:read"
+ - "inspect:read"
+ - "ttl:read"
+ - "config:read"
+ operator:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "cluster:read"
+ - "inspect:read"
+ - "ttl:read"
+ - "config:read"
+ - "overview:read"
+ - "overview:write"
+ - "setup:read"
+ - "setup:write"
+ - "dashboard:read"
+ - "dashboard:write"
+ - "alarm-setup:read"
+ - "alarm-setup:write"
+ - "alarm-rule:read"
+ - "alarm-rule:write"
+ - "rule:read"
+ - "rule:write"
+ - "rule:write:structural"
+ - "rule:delete"
+ - "rule:debug"
+ - "live-debug:read"
+ - "live-debug:write"
+ - "profile:enable"
+ admin:
+ - "*"
+ landingByRole:
+ viewer: /
+ maintainer: /operate/cluster
+ operator: /
+ admin: /
+
+session:
+ ttlMinutes: 60
+ cookieName: horizon_sid
+ cookieSecure: false
+
+audit: { file: ./horizon-audit.jsonl }
+setup: { file: ./horizon-setup.json }
+alarms: { file: ./horizon-alarms.json }
+debugLog: { enabled: false, file: ./horizon-wire.jsonl, maxBodyChars: 8192, redactAuthHeaders: true }
diff --git a/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.local.yaml b/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.local.yaml
new file mode 100644
index 0000000..58477e0
--- /dev/null
+++ b/scripts/.finalize-work/src/.claude/skills/local-boot/horizon.local.yaml
@@ -0,0 +1,111 @@
+# Static local-boot config for the Horizon UI BFF — points at a LOCAL
+# OAP on 127.0.0.1 with NO network auth. Boot via the `local-boot` skill
+# (see SKILL.md) which passes this file through HORIZON_CONFIG as an
+# ABSOLUTE path. The local-user password hashes below are throwaway dev
+# credentials (password == username) — safe to commit; do NOT add real
+# secrets here. For demo-OAP access use horizon.demo.yaml instead, which
+# keeps the OAP password out of git via ${OAP_PASSWORD}.
+
+server:
+ host: 127.0.0.1
+ port: 8081
+
+oap:
+ queryUrl: http://localhost:12800
+ # This OAP serves the debugging/admin REST surface on the SAME port
+ # as GraphQL (12800). Zipkin (9412) may not be exposed locally.
+ adminUrl: http://localhost:12800
+ zipkinUrl: http://localhost:9412/zipkin
+ timeoutMs: 15000
+
+auth:
+ backend: local
+ local:
+ # One account per role for manual menu-RBAC checks. Password == username.
+ users:
+ - username: viewer
+ # password: viewer
+ passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$Gp175hqr+EF2iZ7v1fndvw$w6w9hDI59/UA+CRARChDoGRlR1TkVt6kqzApa021K+0"
+ roles: [viewer]
+ - username: maintainer
+ # password: maintainer
+ passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$w7ULwB3/jzH9FxVoHJ238A$y+qGoX6IPeOoGywLQCpfpAN5VJXcaevoWeJQhaybvQU"
+ roles: [maintainer]
+ - username: operator
+ # password: operator
+ passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$nzoI4RqiobprtzX/mJqe5Q$FY2Hi7mKep0DPHoaE++r/KD++WLUwTgRUFLde87j2Wg"
+ roles: [operator]
+ - username: admin
+ # password: admin
+ passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$joV9AVlyLS3pqq4mLrYokQ$pJLkTKrz9/LzEH6YaFljdz9k8dyBiryjwSB26Diiz9U"
+ roles: [admin]
+
+rbac:
+ enabled: true
+ roles:
+ viewer:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "overview:read"
+ maintainer:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "overview:read"
+ - "cluster:read"
+ - "inspect:read"
+ - "ttl:read"
+ - "config:read"
+ operator:
+ - "metrics:read"
+ - "alarms:read"
+ - "traces:read"
+ - "logs:read"
+ - "topology:read"
+ - "profile:read"
+ - "cluster:read"
+ - "inspect:read"
+ - "ttl:read"
+ - "config:read"
+ - "overview:read"
+ - "overview:write"
+ - "setup:read"
+ - "setup:write"
+ - "dashboard:read"
+ - "dashboard:write"
+ - "alarm-setup:read"
+ - "alarm-setup:write"
+ - "alarm-rule:read"
+ - "alarm-rule:write"
+ - "rule:read"
+ - "rule:write"
+ - "rule:write:structural"
+ - "rule:delete"
+ - "rule:debug"
+ - "live-debug:read"
+ - "live-debug:write"
+ - "profile:enable"
+ admin:
+ - "*"
+ landingByRole:
+ viewer: /
+ maintainer: /operate/cluster
+ operator: /
+ admin: /
+
+session:
+ ttlMinutes: 60
+ cookieName: horizon_sid
+ cookieSecure: false
+
+audit: { file: ./horizon-audit.jsonl }
+setup: { file: ./horizon-setup.json }
+alarms: { file: ./horizon-alarms.json }
+debugLog: { enabled: false, file: ./horizon-wire.jsonl, maxBodyChars: 8192, redactAuthHeaders: true }
diff --git a/scripts/.finalize-work/src/.claude/skills/local-boot/ldap-seed.ldif b/scripts/.finalize-work/src/.claude/skills/local-boot/ldap-seed.ldif
new file mode 100644
index 0000000..f6714fa
--- /dev/null
+++ b/scripts/.finalize-work/src/.claude/skills/local-boot/ldap-seed.ldif
@@ -0,0 +1,50 @@
+dn: ou=people,dc=horizon,dc=test
+objectClass: organizationalUnit
+ou: people
+
+dn: ou=groups,dc=horizon,dc=test
+objectClass: organizationalUnit
+ou: groups
+
+dn: uid=admin,ou=people,dc=horizon,dc=test
+objectClass: inetOrgPerson
+uid: admin
+cn: Admin User
+sn: Admin
+userPassword: admin
+
+dn: uid=operator,ou=people,dc=horizon,dc=test
+objectClass: inetOrgPerson
+uid: operator
+cn: Operator User
+sn: Operator
+userPassword: operator
+
+dn: uid=maintainer,ou=people,dc=horizon,dc=test
+objectClass: inetOrgPerson
+uid: maintainer
+cn: Maintainer User
+sn: Maintainer
+userPassword: maintainer
+
+dn: uid=viewer,ou=people,dc=horizon,dc=test
+objectClass: inetOrgPerson
+uid: viewer
+cn: Viewer User
+sn: Viewer
+userPassword: viewer
+
+dn: cn=horizon-admin,ou=groups,dc=horizon,dc=test
+objectClass: groupOfNames
+cn: horizon-admin
+member: uid=admin,ou=people,dc=horizon,dc=test
+
+dn: cn=sre,ou=groups,dc=horizon,dc=test
+objectClass: groupOfNames
+cn: sre
+member: uid=operator,ou=people,dc=horizon,dc=test
+
+dn: cn=platform,ou=groups,dc=horizon,dc=test
+objectClass: groupOfNames
+cn: platform
+member: uid=maintainer,ou=people,dc=horizon,dc=test
diff --git a/scripts/.finalize-work/src/.dockerignore b/scripts/.finalize-work/src/.dockerignore
new file mode 100644
index 0000000..d3ea2be
--- /dev/null
+++ b/scripts/.finalize-work/src/.dockerignore
@@ -0,0 +1,34 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Denylist style: the image builds FROM SOURCE (see Dockerfile), so the
+# context is the source tree. Exclude only what must NOT enter the build —
+# host-built artifacts (arch-specific node_modules + dist), VCS, CI config,
+# local secrets, and scratch dirs. Everything else (source, lockfile,
+# horizon.example.yaml) is needed by `pnpm install` + `pnpm package`.
+
+**/node_modules
+**/dist
+.git
+.github
+.pnpm-store
+**/.DS_Store
+scripts/.release-work
+scripts/.finalize-work
+.claude
+
+# Local runtime config may carry secrets — never ship it. The committed
+# horizon.example.yaml stays (package.mjs reads it into dist/).
+horizon.yaml
diff --git a/scripts/.finalize-work/src/.github/workflows/ci.yaml b/scripts/.finalize-work/src/.github/workflows/ci.yaml
new file mode 100644
index 0000000..d9781fe
--- /dev/null
+++ b/scripts/.finalize-work/src/.github/workflows/ci.yaml
@@ -0,0 +1,181 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ license-header:
+ name: License header
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - name: Check license header
+ uses: apache/skywalking-eyes@5b7ee1731d036b5aac68f8bd3fc9e6f98ada082e
+
+ dependency-license:
+ # The binary tarball bundles the full production dependency tree — the
+ # BFF runtime deps (dist/node_modules) AND the UI deps Vite compiles
+ # into the static bundle — so ASF policy requires every one of their
+ # licenses reproduced in the binary LICENSE/NOTICE.
+ # collect-dist-licenses.mjs enumerates that tree straight from the pnpm
+ # store (no `pnpm package` needed), and this job fails if either:
+ # 1. a dep carries a Category-X / unknown license (check-dist-licenses), or
+ # 2. the committed binary LICENSE/NOTICE reference is stale (--check) —
+ # i.e. prod deps changed without `pnpm licenses:bin:update`.
+ name: Dependency license
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '24'
+ cache: 'pnpm'
+ - run: pnpm install --frozen-lockfile
+ - name: Verify binary LICENSE/NOTICE (drift + ASF allow/deny)
+ run: pnpm licenses:bin:check
+ - name: Compare source vs. binary LICENSE/NOTICE shape
+ run: |
+ # Source-flavored LICENSE = repo root; must NOT contain the
+ # bundled-dep summary section.
+ if grep -q 'Horizon UI Subcomponents' LICENSE; then
+ echo "ERROR: repo-root LICENSE contains the bundled-dep summary section."
+ echo " That section belongs only in the binary LICENSE."
+ exit 1
+ fi
+ # Committed binary-flavored LICENSE must contain the summary.
+ if ! grep -q 'Horizon UI Subcomponents' dist-material/release-docs/LICENSE; then
+ echo "ERROR: committed binary LICENSE missing 'Horizon UI Subcomponents'."
+ exit 1
+ fi
+ # NOTICE: both must exist and start with the same ASF copyright line.
+ test -f NOTICE && test -f dist-material/release-docs/NOTICE
+ head -1 NOTICE | grep -q 'Apache SkyWalking Horizon UI'
+ head -1 dist-material/release-docs/NOTICE | grep -q 'Apache SkyWalking Horizon UI'
+ echo "src/bin LICENSE+NOTICE shape OK."
+ - name: Upload dependency report
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist-license-report
+ path: |
+ dist/LICENSE
+ dist/NOTICE
+ dist/.dependency-report.json
+
+ type-check:
+ name: Type-check (workspaces)
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '24'
+ cache: 'pnpm'
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm -r run type-check
+
+ build-ui:
+ name: Build · @skywalking-horizon-ui/ui
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '24'
+ cache: 'pnpm'
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm --filter @skywalking-horizon-ui/ui build
+
+ build-bff:
+ name: Build · @skywalking-horizon-ui/bff
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '24'
+ cache: 'pnpm'
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm --filter @skywalking-horizon-ui/bff build
+
+ test:
+ name: Unit tests (workspaces)
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '24'
+ cache: 'pnpm'
+ - run: pnpm install --frozen-lockfile
+ # `pnpm -r` walks every workspace package and runs `test:unit` —
+ # currently @skywalking-horizon-ui/ui (vitest + jsdom) and
+ # @skywalking-horizon-ui/bff (vitest, node env). Packages without
+ # the script (api-client, design-tokens) are skipped automatically.
+ # The scripts already include `vitest run`, so no `--run` forwarding
+ # needed — keeps the CI line robust against pnpm-flag parsing quirks.
+ - run: pnpm -r run test:unit
+
+ required:
+ # Status-check name pinned to "Required" so Apache branch protection
+ # (`.asf.yaml: protected_branches.main.required_status_checks.contexts:
+ # [Required]`) can gate on a single rolled-up signal.
+ if: always() && !cancelled()
+ name: Required
+ needs: [license-header, dependency-license, type-check, build-ui, build-bff, test]
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Check upstream jobs
+ run: |
+ [[ ${{ needs.license-header.result }} == 'success' ]] || exit 1
+ [[ ${{ needs.dependency-license.result }} == 'success' ]] || exit 1
+ [[ ${{ needs.type-check.result }} == 'success' ]] || exit 1
+ [[ ${{ needs.build-ui.result }} == 'success' ]] || exit 1
+ [[ ${{ needs.build-bff.result }} == 'success' ]] || exit 1
+ [[ ${{ needs.test.result }} == 'success' ]] || exit 1
diff --git a/scripts/.finalize-work/src/.github/workflows/publish-image.yaml b/scripts/.finalize-work/src/.github/workflows/publish-image.yaml
new file mode 100644
index 0000000..baaeae2
--- /dev/null
+++ b/scripts/.finalize-work/src/.github/workflows/publish-image.yaml
@@ -0,0 +1,200 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Publish a multi-arch Horizon UI container image to GHCR on:
+# - push to `main` (tagged with `main` + the full commit SHA)
+# - any `v*` tag (tagged with the version + the full commit SHA)
+#
+# Architecture story
+# The Dockerfile builds `dist/` from source inside the image. The
+# resulting `node_modules/` carries `argon2`'s native binding — that
+# binding is platform-specific, so the build must run per-architecture.
+# We build each architecture on its own native GitHub runner
+# (`ubuntu-latest` for amd64, `ubuntu-24.04-arm` for arm64) so the
+# compile is native (no QEMU), push a per-arch SHA-suffix tag, and a
+# final `manifest` job stitches them into a single SHA-canonical OCI
+# manifest list. Pulling the canonical tag from any host (Apple Silicon,
+# AWS Graviton, plain x86_64) selects the right arch automatically — no
+# Rosetta / QEMU emulation needed at run time.
+#
+# Tagging
+# - `:<40-char-sha>` always (canonical, immutable)
+# - `:main` on push to main
+# - `:<ver>` + `:latest` on v* tags (ver = the tag with leading `v` stripped)
+# - `:<major.minor>` on v* tags (e.g. `1.2` from `v1.2.3`)
+#
+# Per-arch interim tags
+# - `:<sha>-amd64` / `:<sha>-arm64` are also pushed so a debugger can
+# pull a specific arch directly. The manifest job is the only thing
+# that exposes the canonical tag without an arch suffix.
+#
+# Only login / setup-buildx / checkout are pulled from third-party
+# actions — everything else (tag computation, buildx build+push,
+# manifest stitching) is shell-driven to stay within ASF infra's
+# third-party-action allow-list. SHAs are vetted at the org level.
+name: publish-image
+
+on:
+ push:
+ branches:
+ - main
+ tags:
+ - 'v*'
+
+permissions:
+ contents: read
+ packages: write
+
+concurrency:
+ group: publish-image-${{ github.ref }}
+ cancel-in-progress: false
+
+jobs:
+ # ── Compute tags once so the per-arch matrix + the manifest job all
+ # ── work from the same set without duplicating the shell math.
+ tags:
+ if: github.repository == 'apache/skywalking-horizon-ui'
+ name: Compute image tags
+ runs-on: ubuntu-latest
+ outputs:
+ base: ${{ steps.tags.outputs.base }}
+ moving: ${{ steps.tags.outputs.moving }}
+ steps:
+ - id: tags
+ env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+ run: |
+ set -eu
+ base="${REGISTRY}/${IMAGE_NAME}"
+ # lower-case the image path — GHCR rejects mixed-case names.
+ base="$(echo "$base" | tr '[:upper:]' '[:lower:]')"
+ sha="${{ github.sha }}"
+ # `moving` = the SET of canonical/moving tags the manifest
+ # job needs to stitch (one per `imagetools create -t ...`).
+ # Space-separated; consumers split on whitespace.
+ moving="${base}:${sha}"
+ if [ "${{ github.ref }}" = "refs/heads/main" ]; then
+ moving="${moving} ${base}:main"
+ fi
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ ver="${GITHUB_REF_NAME#v}"
+ moving="${moving} ${base}:${ver} ${base}:latest"
+ mm="${ver%.*}"
+ if [ "${mm}" != "${ver}" ]; then
+ moving="${moving} ${base}:${mm}"
+ fi
+ fi
+ echo "base=${base}" >> "$GITHUB_OUTPUT"
+ echo "moving=${moving}" >> "$GITHUB_OUTPUT"
+ echo "::notice::Canonical SHA: ${sha}"
+ echo "::notice::Moving tags: ${moving}"
+
+ # ── Per-arch native build. The Dockerfile builds `dist/` from source
+ # ── inside the image, so each native runner compiles the correct
+ # ── argon2 binding for its arch and pushes `<base>:<sha>-<arch>`. The
+ # ── manifest job below stitches the `:<sha>-amd64` + `:<sha>-arm64`
+ # ── pair into the canonical tag set.
+ build:
+ if: github.repository == 'apache/skywalking-horizon-ui'
+ needs: tags
+ name: Build ${{ matrix.arch }}
+ runs-on: ${{ matrix.runner }}
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - runner: ubuntu-latest
+ platform: linux/amd64
+ arch: amd64
+ # Free arm64 runners for public repos. If the org doesn't have
+ # them enabled, this matrix entry fails and the manifest job
+ # below short-circuits — fix is to either enable them on the
+ # org or drop arm64 here and accept Rosetta-emulation on Macs.
+ - runner: ubuntu-24.04-arm
+ platform: linux/arm64
+ arch: arm64
+ env:
+ BASE: ${{ needs.tags.outputs.base }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
+
+ - name: Log in to GHCR
+ uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build + push (${{ matrix.arch }})
+ run: |
+ set -eu
+ docker buildx build \
+ --platform ${{ matrix.platform }} \
+ --file Dockerfile \
+ --label "org.opencontainers.image.source=https://github.com/${{ github.repository }}" \
+ --label "org.opencontainers.image.revision=${{ github.sha }}" \
+ --label "org.opencontainers.image.title=Apache SkyWalking Horizon UI" \
+ --label "org.opencontainers.image.description=Next-generation web UI for Apache SkyWalking." \
+ --label "org.opencontainers.image.licenses=Apache-2.0" \
+ --cache-from type=gha,scope=${{ matrix.arch }} \
+ --cache-to type=gha,mode=max,scope=${{ matrix.arch }} \
+ -t "${BASE}:${{ github.sha }}-${{ matrix.arch }}" \
+ --push \
+ .
+ echo "::notice::Pushed ${BASE}:${{ github.sha }}-${{ matrix.arch }}"
+
+ # ── Stitch the per-arch tags into a single OCI manifest list per
+ # ── canonical/moving tag. Pulling any of these tags from any host
+ # ── selects the matching arch automatically.
+ manifest:
+ if: github.repository == 'apache/skywalking-horizon-ui'
+ needs: [tags, build]
+ name: Stitch multi-arch manifest
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ env:
+ BASE: ${{ needs.tags.outputs.base }}
+ MOVING: ${{ needs.tags.outputs.moving }}
+ steps:
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
+
+ - name: Log in to GHCR
+ uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Create manifest list per moving tag
+ run: |
+ set -eu
+ amd="${BASE}:${{ github.sha }}-amd64"
+ arm="${BASE}:${{ github.sha }}-arm64"
+ for tag in ${MOVING}; do
+ echo "::group::Stitch ${tag}"
+ docker buildx imagetools create -t "${tag}" "${amd}" "${arm}"
+ docker buildx imagetools inspect "${tag}" | head -40
+ echo "::endgroup::"
+ done
+ echo "::notice::Published multi-arch manifest @sha=${{ github.sha }} (amd64+arm64)"
diff --git a/scripts/.finalize-work/src/.gitignore b/scripts/.finalize-work/src/.gitignore
new file mode 100644
index 0000000..0db8ab1
--- /dev/null
+++ b/scripts/.finalize-work/src/.gitignore
@@ -0,0 +1,43 @@
+node_modules
+dist
+dist-ssr
+*.local
+.DS_Store
+
+# Claude Code per-user settings — user-specific allow-list, not for git.
+.claude/settings.local.json
+
+# Editor
+.idea
+.vscode/*
+!.vscode/extensions.json
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Test/coverage
+coverage
+*.lcov
+.nyc_output
+
+# Build artifacts
+*.tsbuildinfo
+
+# OS
+Thumbs.db
+
+# BFF runtime files
+horizon.yaml
+horizon-audit.jsonl
+horizon-wire.jsonl
+horizon-setup.json
diff --git a/scripts/.finalize-work/src/.licenserc.yaml b/scripts/.finalize-work/src/.licenserc.yaml
new file mode 100644
index 0000000..a475456
--- /dev/null
+++ b/scripts/.finalize-work/src/.licenserc.yaml
@@ -0,0 +1,88 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+header:
+ license:
+ spdx-id: Apache-2.0
+ copyright-owner: Apache Software Foundation
+
+ paths-ignore:
+ - '.github/ISSUE_TEMPLATE'
+ - '.github/PULL_REQUEST_TEMPLATE'
+ - '**/.gitignore'
+ - '**/*.md'
+ - '**/*.json'
+ - '**/*.txt'
+ - '**/*.lock'
+ - '**/*.svg'
+ - '**/*.ico'
+ - '**/*.png'
+ - '**/*.jpg'
+ - '**/*.webp'
+ - '**/*.woff'
+ - '**/*.woff2'
+ - 'LICENSE'
+ - 'NOTICE'
+ - 'HEADER'
+ - '**/dist/**'
+ - '**/node_modules/**'
+ # Release scratch dirs: release.sh / release-finalize.sh clone + stage
+ # the repo here. The check must not recurse into that nested copy (its
+ # paths don't match the repo-root-relative ignores below, e.g. .claude/**).
+ - '**/.release-work/**'
+ - '**/.finalize-work/**'
+ - 'docs/**'
+ # Agent / dev tooling, not shippable source — skill configs (e.g.
+ # local-boot's horizon.*.yaml) carry no ASF header.
+ - '.claude/**'
+ - 'pnpm-lock.yaml'
+ - '.browserslistrc'
+ - '.prettierrc'
+ - '.eslintrc*'
+ - '.editorconfig'
+ - 'src/types/auto-imports.d.ts'
+ - 'src/types/components.d.ts'
+ - 'public/**'
+ # LICENSE.tpl / NOTICE.tpl in dist-material/release-docs/ render the
+ # binary tarball's LICENSE / NOTICE (scripts/collect-dist-licenses.mjs).
+ # Their contents ARE the Apache 2.0 license text and the ASF notice
+ # text — an extra license-header comment would bleed into the output.
+ - 'dist-material/release-docs/**/*.tpl'
+
+ comment: on-failure
+
+dependency:
+ files:
+ - apps/ui/package.json
+ - apps/bff/package.json
+ - packages/api-client/package.json
+ - packages/design-tokens/package.json
+ - packages/templates/package.json
+ excludes:
+ - name: '@skywalking-horizon-ui/*' # Self workspace packages
+ recursive: true
+ # Human-asserted licenses for bundled deps whose package.json omits or
+ # mis-declares `license`. This is skywalking-eyes' native unknown-license
+ # mechanism; it is ALSO consumed by scripts/collect-dist-licenses.mjs when
+ # it builds the binary tarball's LICENSE. Each entry MUST be verified
+ # against the package's own LICENSE/COPYING file (reproduced under
+ # dist/licenses/) before being added.
+ licenses:
+ - name: vue-grid-layout
+ version: 3.0.0-beta1
+ license: MIT # ships an MIT LICENSE file; package.json omits `license`
diff --git a/scripts/.finalize-work/src/CHANGELOG.md b/scripts/.finalize-work/src/CHANGELOG.md
new file mode 100644
index 0000000..24aedda
--- /dev/null
+++ b/scripts/.finalize-work/src/CHANGELOG.md
@@ -0,0 +1,345 @@
+# Changelog
+
+Notable changes to Apache SkyWalking Horizon UI, written from the operator's
+point of view — what's new on screen and what's now possible, not the
+file-by-file implementation. For per-commit detail, see the git log.
+
+The version line is shared by every package in the monorepo (apps + shared
+packages) plus the BFF's `HORIZON_VERSION` default.
+
+## 0.5.0
+
+First Apache-style release cut from this repo: source + binary tarballs,
+GPG-signed and SHA-512 checksummed, with a self-contained binary that
+boots via `node server.js` and no `pnpm install` step. Binary distribution
+ships a regenerated `LICENSE` + `NOTICE` that enumerate every bundled
+third-party package — produced by `scripts/collect-dist-licenses.mjs`
+during packaging and validated against a deny-list before signing.
+
+### Profiling
+
+- **pprof (Go) profiling** is fully wired: pick one event per task (CPU /
+ HEAP / BLOCK / MUTEX / GOROUTINE / ALLOCS / THREADCREATE), with duration
+ shown for CPU/BLOCK/MUTEX and a sampling-rate field for BLOCK/MUTEX. Create
+ and analyze both match OAP's single-event pprof schema.
+- **eBPF profiling** gets a reworked process picker — click a row to expand
+ its full attributes, selection lives on the checkbox, anchored pop-out — a
+ refresh button on every task list, Intl-formatted times, and a hover-info
+ frame on the flame graph. Flame-graph thrash on re-analyze is gone.
+- The shared flame graph fixes "% of root" (it read a never-aggregated count),
+ highlights the selected frame across all four profilers, and shows a single
+ hover card (the library's duplicate native tooltip is suppressed).
+- After creating any profiling task (trace / async / eBPF / network / pprof)
+ the list now polls up to 4× at 10s until the new task shows up, instead of
+ leaving a stale pre-create list.
+
+### Network profiling & process topology
+
+- A booster-style honeycomb process topology: pods as hexagons, peers hugging
+ the boundary, animated protocol-coloured edges (HTTP/TCP/TLS), a node
+ pop-over, and a wide client | server edge-metric dashboard. Network task
+ creation and the task-list query now use OAP's schema field names.
+
+### Platform monitoring (operate)
+
+- Two new read-only operate pages: **Data retention** (TTL — `getRecordsTTL` /
+ `getMetricsTTL`) and **OAP configuration** (the admin-port config dump, with
+ OAP-masked secrets). Gated on new `ttl:read` / `config:read` verbs granted to
+ maintainer and above. Data retention now loads on non-BanyanDB backends too
+ (the `metadata` TTL field is optional).
+- The operate sidebar now leads with a single **Platform monitoring** group
+ (cluster status, data retention, OAP configuration) above the per-layer
+ self-observability dashboards.
+
+### Dashboards & templates
+
+- **The global time picker now drives dashboards.** Layer dashboards query
+ OAP at the picker's window and precision (MINUTE / HOUR / DAY) instead of a
+ fixed last-hour minute window, and line charts label the x-axis with real
+ times per step (e.g. `MM-DD` for a 30-day view) rather than `-Nm`.
+- **New `table` widget** for label-dimensioned metrics — pod phase per service,
+ node condition, deployment replicas, etc. — rendered as one column per label
+ (e.g. `Condition | Node`) instead of a scalar card or a misleading flat line.
+ The K8S dashboards (and kong / mongodb / elasticsearch) now use it where
+ upstream booster-ui does; widgets that were charting a single `latest(…)`
+ value as a line are now cards. The K8S Cluster view is realigned to the
+ upstream layout (totals cards · resource lines · status tables).
+- **Edit locally, publish on your terms.** Saving a dashboard/overview template
+ now writes the local bundled copy (so the edit renders immediately for
+ preview) and marks it diverged — nothing reaches OAP until you press
+ **Sync all to OAP**, which pushes only the templates that differ, behind a
+ confirmation listing exactly what will be written. A post-save tip spells out
+ that the change is local-only until published.
+- **Local-vs-remote, made explicit.** When local edits diverge from OAP, a
+ per-session prompt (by menu name, not file name) asks which to render —
+ **keep my local edits** (preview) or **use live** (which overwrites the local
+ copy with the remote version, confirmed). The layer-templates admin page
+ carries the same Local/Remote display toggle next to Sync all, and a
+ **Diverged only** filter; each diverged layer shows a yellow warning icon in
+ the sidebar.
+
+### Traces
+
+- The native trace view auto-selects OAP's trace-query API — `queryTraces`
+ (whole trace inline, BanyanDB) vs `queryBasicTraces` (segment list + a
+ per-trace fetch on click, every other backend) — and a banner states which
+ is in use; in segment-list mode the list reads "Segments" and a click loads
+ the full trace.
+- Span kind (Entry / Exit / Local) renders as a colored word, not a filled pill.
+
+### Auth, RBAC & resilience
+
+- Every OAP call — GraphQL, admin REST, and Zipkin — now carries the
+ configured basic-auth credentials, so a secured OAP no longer 401s pages.
+- The sidebar is RBAC-gated by read verb, the Roles page shows a per-role
+ menu-visibility matrix, and the Users page labels per-node "Active (24h)" /
+ "Last seen" honestly (these are tracked per BFF replica, not cluster-wide).
+- **Routes are verb-gated, not just menus.** A user without the required read
+ verb is bounced from a restricted page (e.g. a viewer can no longer reach
+ Cluster Status via the topbar OAP chip or a direct URL); the chip only links
+ there for `cluster:read`. This sits on top of the existing per-route BFF verb
+ enforcement.
+- **LDAP** resolves group membership with the service account, not the
+ logging-in user — directories that hide the group subtree from ordinary users
+ no longer collapse every login to the fallback role.
+- When OAP is unreachable the menu and admin loaders fall back to bundled
+ templates, and non-JSON OAP responses surface a clear diagnostic.
+
+### Smaller touches
+
+- Top-N widgets get hover tooltips for long names and a title-bar pop-out to
+ the full ranked list; redundant single-service name prefixes are dropped.
+- The admin template-diff modal is a wide side-by-side view with labelled
+ bundled-vs-OAP columns and an explanation of what the template drives; the
+ layer-dashboards admin rail gains an in-page search.
+- Per-layer alarm filtering uses the singular `queryAlarms` layer condition.
+- Dependency hygiene for the release: `dompurify` ≥ 3.3.2 and
+ `@fastify/static` ≥ 9.1.1 (clears the known advisories); the `general`
+ layer drops `networkProfiling`, which is instance-scoped to k8s / mesh.
+
+## 0.4.0
+
+OAP becomes the runtime source of truth for UI templates, the 5-theme system
+lands, and the app supports being served behind a gateway prefix.
+
+### Templates synced to OAP
+
+- Five reserved template families now live on OAP's UI-template REST surface
+ (`/ui-management/templates*` on the admin port): overview dashboards, per-layer
+ dashboards, alert page setup, theme selection, time-defaults. Bundled JSON
+ ships as the seed + read-only fallback.
+- One-shot seed on BFF boot pushes any missing bundled template to OAP; runtime
+ sync is read-only with a 30-second single-flight cache.
+- New admin endpoints: `GET /api/admin/templates/sync-status`,
+ `POST /api/admin/templates/save`, `POST /api/admin/templates/resync`,
+ `POST /api/admin/templates/:name/push-bundled`.
+- When the admin port is unreachable, every admin page goes read-only with a
+ red banner; Save / Create / Delete are disabled; render falls back to bundled.
+- Diverged rows surface a "Show diff & reset" Monaco modal with a
+ destructive-confirm (type the template key to arm reset).
+
+### Themes
+
+- Five bundled themes — **Horizon** (default), **Meridian**, **Obsidian**,
+ **Daybreak**, **Aurora** — each shipping a complete token set (bg, fg,
+ accent, info/ok/warn/err, font, radius, density).
+- New `/admin/global-defaults` admin page replaces the old "Setup" link.
+ Theme picker uses preview cards lifted from the design (hero strip,
+ mini-app mockup with Primary/Tonal/Ghost buttons, KPI tiles, sparkline,
+ density/font/radius badges).
+- Per-user theme override via a labelled topbar chip — three-tier resolution
+ `localStorage user → OAP org default → bundled`, written to
+ `<html data-theme>` / `<html data-appearance>` synchronously on boot so
+ the pre-auth login page already respects the local override.
+- Sidebar SkyWalking logo swaps to the official brand blue (`#1368B3`) on
+ light-appearance themes.
+- Widget series colors (Zipkin trace palette, AlarmSnapshotChart,
+ AlarmsTimeline) track the active theme's `--sw-accent` via a shared
+ `readAccent()` util.
+- Sign-in button gradient derives both stops from the theme accent.
+
+### Time defaults
+
+- `/admin/global-defaults` also owns the global picker's default window
+ (60 minutes shipped). OAP `step` precision is derived from window size —
+ ≤ 4 h MINUTE, 6 h–14 d HOUR, ≥ 30 d DAY — and surfaced inline on the page.
+- Per-user override in the topbar time picker: "Save as my default" /
+ "Reset to org default".
+
+### Reliability + diagnostics
+
+- Topology cluster boundary now grows to encompass dragged nodes; the chip
+ moved inside the cluster header so it stays visible at any drag position.
+- Alarms page gains an **Other** KPI tile that surfaces the residual count
+ between `Active` and the sum of pinned-layer chips — `Active = General +
+ Mesh + Other` reconciles even when alarms land in unmapped layers.
+- Overview "Active alarms" widget now reads the admin's configured
+ `defaultWindowMs` from `/admin/alert-page-setup`; all three alarm
+ surfaces (overview widget, alarms page, topbar badge) share one window.
+- Every backend call failure (network throw or non-2xx) writes a
+ `pushEvent('api', 'err', …)` into the debug event log with the BFF's
+ `code` / `message` envelope inlined when present.
+- Dashboards with more than 40 widgets (e.g. the General/instance page,
+ 56 widgets) now succeed: the UI splits oversize requests into ≤40-widget
+ chunks fired in parallel, then merges results.
+
+### Deployment
+
+- Gateway-prefix support: `BffClient.request()` prepends
+ `import.meta.env.BASE_URL` to every API path. Build with
+ `vite build --base=/horizon/` and a gateway that strips the prefix and
+ the SPA + every API call resolves cleanly under the sub-path.
+- Cluster Status route corrected from `/admin/cluster` → `/operate/cluster`
+ (the prior default 404'd because no route by that name existed).
+
+### Cleanup
+
+- Documentation rewritten as an orientation map; the left-side menu is the
+ canonical navigation now. All `SWIP-*` references removed from
+ user-visible text and docs.
+- "Coming in Phase 6 / 7" placeholder strip on Cluster Status removed.
+- Dead code dropped — `LandingView.vue`, `LayerTabPlaceholder.vue`, the
+ orphaned disk-write template routes (`POST /api/admin/overview-templates/:id`
+ + `POST /api/admin/layer-templates/:key`), and stale `Phase X` markers
+ across BFF + UI + docs.
+- The OAP UTC-offset chip is gone from the topbar; the health dot stays.
+
+## 0.3.0
+
+The shell unifies, the operate stack lands, and the first round of public
+documentation ships.
+
+### Operate stack
+
+- **Alarms page** — incident-merged active-alarms view, severity tabs,
+ alarm list with right-side detail (trigger expression, channel routing),
+ inline Live Debug card (Run / Step / Pause / Copy as MQE, execution-trace
+ ladder with per-step output + latency, matched entities, eval-window
+ chart, raw OAP response).
+- **Inspect** — metric catalog + entity enumerator with search, type
+ filter, scope (Service / Instance / Endpoint / Process / All), and
+ source attribution.
+- **Live Debugger** — MAL / LAL / OAL session start, poll, stop. Per-node
+ status fan-out, sample payloads, capture history with replay-ready
+ recordings.
+- **Profiling** — flame graph + stack table over five profilers:
+ trace-driven thread profiling, eBPF CPU/off-CPU, JVM async-profiler,
+ network profiling (process conversation graph), Go pprof.
+- **Zipkin trace explorer** — service / span search, waterfall popout
+ with per-service color bands, sticky time-axis.
+- **Overview dashboards** — cross-layer war-room views (Services, Mesh)
+ with per-layer KPI tiles, alarm rails, and the existing chart widgets.
+
+### Auth + access control
+
+- Local + LDAP authentication backends. Break-glass admin honored only
+ when `backend: ldap` AND the LDAP probe is failing.
+- Three admin pages — Users, Auth status, Roles & permissions.
+- 4 built-in roles (viewer / maintainer / operator / admin) and a
+ 28-verb permission model. Every BFF route gated by a single policy
+ table.
+- Login view redesigned (canyon hero, status pill, configured-backend
+ banner).
+
+### Reliability + UX
+
+- Cascade-clear, then load — every dependent area visibly resets and
+ shows "Reading data…" between an upstream control change (service /
+ instance / endpoint pick, time-range change, layer / scope nav) and
+ the new data landing. No silent freezes; no stale value sitting under
+ a spinner.
+- Global time picker in the topbar wired into the landing + widget
+ query keys; the picker only applies to dashboards / overviews (triage
+ pages keep their own per-page time).
+- Single-shot bundle preload: layer dashboards + overview list arrive
+ in one round-trip, cached in localStorage with ETag revalidation.
+- Framework event ticker in the topbar replaces breadcrumb+search;
+ Admin-toggled debug panel surfaces a 200-event buffer with operator
+ click capture.
+- Auto-pick first instance / endpoint when a scope needs one and the
+ list is non-empty.
+- Topology + dashboard fixes, multi-layer service attribution, sticky
+ service selection across navigations.
+
+### Documentation
+
+- First public docs tree (`docs/`) — Setup, Compatibility, Access
+ Control, Customization, Components, Operate. Lives in-repo and
+ publishes to skywalking.apache.org.
+
+### Container + CI
+
+- Real `packages/*` builds + self-contained `dist/` + copy-in image
+ (no compile in the container).
+- Zero-config boot: image defaults `HORIZON_SERVER_HOST=0.0.0.0`.
+- Multi-arch publish-image — native amd64 + arm64 builds, OCI manifest
+ list.
+- Unit-test job in CI; 107 UTs covering entity-scope construction +
+ routing decisions.
+
+## 0.2.0
+
+Per-layer dashboards become real, the layer-template editor ships, and
+topology gets its booster-ui port.
+
+### Per-layer dashboards
+
+- Real widget grid per layer driven by JSON templates. 43 layer
+ dashboards migrated from booster-ui.
+- Per-scope widget sets: each layer template defines its own `service`,
+ `instance`, `endpoint`, `topology`, `traces`, `logs`, profiling
+ variants.
+- Visibility predicates per widget (`visibleWhen`) so MQ / DB widgets
+ only render when the relevant metrics are reporting.
+
+### Layer admin
+
+- Read-only template browser, then full edit UI: components editor
+ (toggle which per-layer views exist), metrics editor (header columns),
+ separate Overview tile card, scope-aware visibleWhen hints.
+
+### Service deep-dive
+
+- APIs widget (formerly Services), MQ widgets gated by visibleWhen,
+ TopList multi-expression switcher with MQE preview in tooltip,
+ smaller widget height, per-metric color alignment, dual-axis MQ.
+
+### Topology
+
+- Polished linear-chain variant, dual-panel detail, per-side line
+ charts. Drag-to-move + barycentric layout for smaller graphs.
+ RPM-only chip variant. Istio renamed.
+
+### Logs
+
+- Legend at top of table (drop service facet duplication), workflow
+ notes.
+
+### Charting
+
+- TimeChart: legend formatting fix for dual-axis widgets, value dots,
+ tooltip escape for clipped charts, no more legend / axis-name
+ crowding at chart top.
+
+### Sidebar + chrome
+
+- Group toggle + group click cascades to first layer's first tab.
+- Topbar 60m widget format hints (int / decimal / compact).
+- Per-layer image pipeline (icons) shipped.
+
+## 0.1.0
+
+Foundational scaffolding. The shell renders, auth works, OAP is reachable,
+and CI is green. No operator-facing data surfaces yet.
+
+- pnpm monorepo: `apps/ui` (Vue 3 + Vite), `apps/bff` (Fastify), shared
+ `packages/api-client` (typed REST + GraphQL clients), shared
+ `packages/design-tokens` (CSS custom properties).
+- BFF — Fastify skeleton with `horizon.yaml` config + hot reload, local
+ auth (argon2 + cookie sessions), RBAC verb gating + JSONL audit log,
+ OAP proxy with cluster fan-out + preflight.
+- UI — AppShell (sidebar, topbar) with design tokens, Pinia auth store
+ with on-401 redirect, login view with route guard + sign-out, stub
+ admin / operate pages.
+- CI — monorepo workspace build + dependency license check via
+ `skywalking-eyes`.
diff --git a/scripts/.finalize-work/src/CLAUDE.md b/scripts/.finalize-work/src/CLAUDE.md
new file mode 100644
index 0000000..f6d7248
--- /dev/null
+++ b/scripts/.finalize-work/src/CLAUDE.md
@@ -0,0 +1,100 @@
+# CLAUDE.md - AI Assistant Guide for Apache SkyWalking Horizon UI
+
+This file states the **principles** for working in this codebase. It is not a how-to. Code details (directory layout, tech-stack versions, function names, step-by-step recipes) belong in the code itself — read the code when you need them.
+
+## What this project is
+
+**Horizon UI** is the next-generation web UI for [Apache SkyWalking](https://github.com/apache/skywalking). The goal is **feature parity** with [skywalking-booster-ui](https://github.com/apache/skywalking-booster-ui) on the **same OAP GraphQL query-protocol and MQE**, with a modernized, dense, dark-first design. This is a **greenfield rewrite**, not a fork. Backend APIs do not change.
+
+## How to work (the only workflow that matters)
+
+> **Correctness comes first. Speed comes from getting it right the first time, not from skipping steps.**
+
+Every non-trivial change follows the same loop:
+
+1. **Read the code.** Read it end-to-end along the path you intend to change: call site → handler → response shape → consumer. Comments, this file, and prior conversation are *reference* — the source of truth is the current code on disk.
+2. **Implement.** Match what the surrounding code already does. If you have to invent a pattern, you probably haven't finished step 1.
+3. **Validate against a live OAP.** Run the change against a real OAP server and confirm the wire request/response and the rendered UI. Type-checks and unit tests verify code, not feature behavior.
+
+If no OAP is available to validate against, **stop and ask the developer to provide one** (URL, credentials, demo endpoint, port-forward — whatever). Do not guess at wire shapes, do not mock the data and call it done, do not declare the work complete from a green type-check alone. "I couldn't validate" is an honest, acceptable outcome — silently shipping unvalidated changes is not.
+
+When you can't reproduce the user's symptom locally, say so. Don't invent a fix.
+
+## Backend compatibility
+
+The UI talks to OAP through the **GraphQL query-protocol** (same as booster-ui) and through OAP's admin REST surface. Both contracts are **fixed** — owned by the skywalking repo, not this one.
+
+- **Do not invent fields.** If a screen needs data the protocol doesn't expose, flag it. The right fix is a query-protocol change upstream, not a UI hack or a BFF-side fabrication.
+- **The schemas and Java implementations are the authoritative spec** — read them (`oap-server/server-query-plugin/.../query-protocol/*.graphqls` and `oap-server/server-core/.../query/`) before guessing at a wire shape. The demo at `demo.skywalking.apache.org/graphql` (basic-auth `skywalking:skywalking`) is a fine smoke-test target.
+- **Booster-ui is the working reference.** When in doubt about how a query is shaped or paged, look at how booster-ui does it.
+
+### Metric entity-scope is load-bearing
+
+Every OAP metric lives under exactly one entity scope (Service / ServiceInstance / Endpoint / relations / Process / All). OAP does not auto-rollup between scopes — querying at the wrong scope returns empty results regardless of MQE wrapping. Before adding or moving a metric, verify its scope against the OAP catalog and confirm it matches the page that will render it. Never invent a BFF-side rollup to bridge a scope mismatch — move the metric or leave the slot empty.
+
+### Time, step, and timezone
+
+- **Step precision is page-family-specific.** Dashboards / overviews / landing scale step with the rolling window (MINUTE / HOUR / DAY). Alarms / traces / logs / live debugger use SECOND because they query event-style data anchored at second precision — MINUTE rounding chops off the most recent (most interesting) events. MQE traffic backdrops use MINUTE because metrics are aggregated at minute granularity.
+- **String format is determined by step.** Mixing them throws `verifyDateTimeString` on OAP. Read `DurationUtils.java` in the skywalking repo for the canonical mapping.
+- **OAP has a per-request bucket cap.** Long windows must be chunked. Storage backends impose stricter caps that vary by backend — probe, don't assume.
+- **All time strings are OAP-server local.** Not UTC, not browser-local. The server's offset is exposed via `getTimeInfo`. The BFF owns this conversion; the UI displays in browser-local (echarts handles ms → local natively).
+- **Per-page vs. global time.** The topbar time picker applies only to layer dashboards + overviews. Triage / investigation pages (alarms, traces, logs, profilings, live debugger) own their own time range — do not subscribe them to the global ticker.
+- **Picker wiring is a two-sided contract.** The time range only reaches OAP when the UI composable forwards it AND the BFF route accepts it. Verify the actual request, not the intent.
+
+### Other OAP sharp edges
+
+- **Storage backends have undocumented limits.** Page sizes, nested selections, and per-record sub-queries fail at backend-specific thresholds. Degrade list queries to the cheapest selection that satisfies the screen; probe before defaulting.
+- **OAP IDs are not always per-record unique.** Some wire `id` fields key on the alarmed/related entity, not the firing instance. Disambiguate composite keys with timestamp before using `id` as a row key.
+- **Widget type follows MQE shape.** A widget whose MQE collapses to a single scalar must be `type: "card"`, not `type: "line"`. The tell-tale is the outermost call: `latest(...)`, `max(...)`, `min(...)`, `avg(<plain-metric>)`, `sum(<plain-metric>)` all reduce the window to one number — line-charting a single point is wasteful and misleads operators into thinking the metric is time-varying. Series-shaped wrappers (`relabels(...)`, `top_n(...)`, `histogram*(...)`, `aggregate_labels(...)` [...]
+
+## Design source of truth
+
+Design tokens live in the runtime token CSS (`apps/ui/src/assets/styles/tokens.css`) — that copy is canonical. The early-build HTML/JSX prototype bundle has been retired now that every screen has a Vue implementation with visual sign-off. When a new screen is needed, the existing dark-dense vocabulary in the rendered UI is the spec; match it.
+
+`docs/` is now the **public website docs** tree (committed, flat layout, see `docs/menu.yml`). Do not put planning notes, research dumps, or design prototypes there — those stay out of git.
+
+**Docs are written for the end user (operator / dashboard author), not the contributor.** Document what a feature *does*, how to *configure* it (YAML, fields, recipes), and how to *operate / troubleshoot* it. Do **not** document the internal code workflow: no step-by-step algorithms ("1. service-bind 2. user search 3. …"), no source-file paths (`apps/bff/src/...`), no internal function / composable / route names, no "the BFF then fans out / chunks / probes …" implementation narration. If [...]
+
+## Things that are non-negotiable
+
+- **TypeScript strict.** No `any` outside `.d.ts` shims.
+- **Charts are wrapped.** Never instantiate ECharts directly in a view — always go through the chart components. Same rule for D3: composable owns the lifecycle, tears down on unmount. (This lets us swap libraries and centralize theming/disposal.)
+- **One icon component.** No icon font, no inlined SVG one-offs.
+- **No CSS-in-JS, no Tailwind.** The design is built on CSS custom properties; Tailwind fights the token system.
+- **License headers** on every `.ts` / `.vue` / `.js` / `.yaml` / `.yml` / `.css` / `.scss`. JSON, Markdown, lock files, and generated `.d.ts` are excluded — see `.licenserc.yaml`. CI enforces; run `license-eye -c .licenserc.yaml header check` before pushing.
+- **Multi-layer is the spine.** Almost every screen has "this could appear in multiple layers" semantics. Build for that from day one.
+- **Synced crosshairs.** Multiple time-series on a dashboard share one cursor. Don't build charts that ignore this.
+- **Density beats whitespace.** This is an observability-class UI; information density is a feature.
+- **Cascade-clear, then load.** When an upstream control changes (service / instance / endpoint pick, time-range change, layer / scope nav) and the downstream queries have to refire, the dependent area must visibly RESET first and show an explicit "Reading data…" (or equivalent) hint while the new query is in flight. Never leave the prior value sitting under a spinner — operators read it as the new state and trust broken data. Never let the page sit silently between the click and the res [...]
+- **MQE is a core capability**, not a config-screen afterthought. User-editable, syntax-highlighted, debuggable.
+- **Admin views use the same look.** LDAP/RBAC/admin are dark, dense, design-tokens — not a separate "settings" UI. Alarms are read-only on the UI side; recovery is backend-automatic (no acknowledge/close/silence actions).
+- **Comments earn their keep.** A comment should carry only what is **not obvious from reading the core code** — a highlight, a special case, or an edge case worth flagging for a future reviewer. Two valid uses: (1) introduces an API — what a module / component / exported function is for and how callers should think about it; (2) highlights a non-obvious gotcha — a hidden invariant, a workaround for a specific upstream quirk, a subtle scope/timing constraint, an edge case the reader woul [...]
+- **Layering — BFF.** `apps/bff/src/` is grouped by role, and the flow is one-directional: `http → logic → client → OAP`.
+ - `http/{query,config,admin}/` — Fastify route handlers only. Thin: parse, dispatch to logic, shape the reply. No OAP I/O directly.
+ - `logic/<domain>/` — domain orchestration + background timers (alarms store, layer/overview/setup loaders, preflight status check, inspect parsers, dashboard defaults). No HTTP framework, no OAP fetch detail.
+ - `client/` — the only place that talks to OAP (GraphQL + admin REST + Zipkin + cluster fan-out). Wrap upstream errors into typed envelopes here.
+ - `util/` — pure helpers used anywhere (time formatting, MQE target/catalog cache, trace-protocol cache).
+ - `user/` + `rbac/` — session/auth + verb policy, enforced at the http edge.
+ Don't import `client/` from `http/` directly; route through `logic/` so the orchestration layer stays the seam.
+- **Layering — UI.** `apps/ui/src/` follows the same role-based grouping. The guiding rule is **feature code stays cohesive with its feature; shared code is feature-AGNOSTIC only** (fonts, styles, formatters, generic primitives, charts wrappers). A component or composable that knows about a feature's data lives WITH the feature, not in a shared pile. Features don't import from each other.
+ - **`api/`** — façade `bff.<scope>.<method>()` over the BFF. Only path to HTTP; no `fetch()` calls anywhere else.
+ - **`shell/`** — chrome every page lives in (AppShell / AppSidebar / AppTopbar / GlobalConnectivityBanner / AdminFeatureWarning / PlaceholderView / LandingView), plus `router/index.ts` and the framework-level composables the sidebar/topbar need (`useLayers`, `useLandingOrder`, `useOapInfo`, `useAdminFeatures`). Knows about layers + routes, never about specific feature data.
+ - **`controls/`** — cross-cutting controls owned by the topbar / shell. The time-range store, the auto-refresh ticker + its subscriber, the per-session client id. Pages subscribe, never own.
+ - **`state/`** — global app state (`auth`, `setup`). Pinia stores that survive route changes.
+ - **`features/<feature>/`** — static feature pages. Each folder is fully self-contained: its views, its composables, its feature-specific components. Operate sub-features (`cluster/`, `inspect/`, `dsl/`, `live-debug/`) share their cross-cutting bits via `features/operate/_shared/` (Modal / MonacoYaml / MonacoDiff / RuleCard / DestructiveConfirm / grouping).
+ - **`layer/<tab>/`** — the per-layer drill-down stack. Top-level files are the shell (`LayerShell`, `LayerServiceSelector`, `LayerTabPlaceholder`) plus shared layer composables (`useLayerLanding`, `useLayerEndpoints`, `useLayerInstances`, `useSelectedService/Instance/Endpoint`). Each tab subfolder owns its view + composables + tab-specific components (e.g. `layer/traces/` contains `LayerTracesView.vue` + `NativeTraceWaterfall.vue` + `TracePopout.vue` + `useLayerTraces.ts`).
+ - **`render/`** — template-driven render. `render/overview/` and `render/layer-dashboard/` are generic renderers driven by JSON templates from the BFF; `render/widgets/` holds the reusable widget primitives (AlarmsWidget, MetricWidget, ServiceCountWidget, …). New dashboards mean new templates, not new Vue files.
+ - **`components/{primitives,charts,icons}/`** — feature-agnostic shared building blocks. Stateless, no business logic. If a component starts needing feature data, move it INTO the feature; don't enrich the shared pile.
+ - **`monaco/`, `utils/`, `assets/`** — same shared-is-feature-agnostic rule: editor setup, formatters, stylesheets, icon SVGs. New helpers land here only if more than one feature needs them and they don't carry feature semantics.
+
+## Commits & PRs
+
+**Never** add `Co-Authored-By: Claude` (or any AI / Anthropic / claude.com / `noreply@anthropic.com` line) to commit messages or PR bodies. Do not append the "🤖 Generated with Claude Code" footer. Per-project directive.
+
+## Common AI failure modes to avoid
+
+1. **Skipping the read.** If you change a route, composable, store, or template without reading it end-to-end first, you will break something subtle. Read first, every time.
+2. **Validating with type-checks instead of OAP.** Green `tsc` and green tests do not prove the feature works. If you can't hit a real OAP, ask for one.
+3. **Blanket reverts.** `git checkout <file>` discards anyone else's uncommitted changes too. Diff first, revert by hunk.
+4. **Faking backend data to "unblock" yourself.** A mocked response that shapes nothing real is worse than no response — it hides the real wire mismatch until much later. Ask for OAP access.
+5. **Inventing layouts.** The rendered UI is the visual spec; match the existing dark-dense vocabulary rather than introducing new conventions. If something is genuinely new, ask before designing.
diff --git a/scripts/.finalize-work/src/Dockerfile b/scripts/.finalize-work/src/Dockerfile
new file mode 100644
index 0000000..b54ffb2
--- /dev/null
+++ b/scripts/.finalize-work/src/Dockerfile
@@ -0,0 +1,80 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# From-source, multi-arch image. Single source of truth for every image
+# build: CI (GHCR), local dev, and the manual Docker Hub release push.
+#
+# Why from-source (not copy-in): `dist/node_modules` carries argon2's
+# native binding, which is architecture-specific. Building `dist/` INSIDE
+# the image — once per target platform — lets one `buildx --platform
+# linux/amd64,linux/arm64` invocation compile the correct binding for each
+# arch (native on a matching runner, QEMU-emulated otherwise). A copy-in
+# image cannot do this from a single host.
+#
+# docker build -t horizon-ui:local . # single-arch dev build
+# docker buildx build --platform linux/amd64,linux/arm64 -t … --push .
+#
+# Trade-off: the build needs the network (`pnpm install`) — but only in the
+# throwaway build stage. The final shipped stage is network-free.
+
+# ---- build stage: compile the self-contained dist for THIS platform ----
+# Throwaway BUILD ENVIRONMENT — uses the network (`pnpm install`) and
+# carries the compiler toolchain. Discarded; only the final stage ships.
+FROM node:24-alpine AS build
+WORKDIR /src
+
+# Toolchain for argon2's native build (node-gyp needs python3 + make + g++).
+RUN apk add --no-cache python3 make g++
+
+# corepack ships with node:24; pin pnpm to the workspace version.
+RUN corepack enable && corepack prepare pnpm@10.33.2 --activate
+
+# Copy the whole source tree. `.dockerignore` keeps host-built artifacts
+# (node_modules, dist) out of the context so nothing arch-specific leaks in.
+COPY . .
+
+RUN pnpm install --frozen-lockfile
+RUN pnpm package
+
+# ---- runtime stage: the shipped image — no toolchain, no network ----
+FROM node:24-alpine
+WORKDIR /app
+
+RUN addgroup -S horizon && adduser -S -G horizon horizon
+
+COPY --from=build /src/dist/server.js ./server.js
+COPY --from=build /src/dist/package.json ./package.json
+COPY --from=build /src/dist/node_modules ./node_modules
+COPY --from=build /src/dist/static ./static
+COPY --from=build /src/dist/horizon.example.yaml ./horizon.example.yaml
+
+COPY --from=build --chown=horizon:horizon /src/dist/bundled_templates ./bundled_templates
+
+RUN mkdir -p /data && chown horizon:horizon /data
+VOLUME ["/data"]
+
+ENV NODE_ENV=production \
+ HORIZON_SERVER_HOST=0.0.0.0 \
+ HORIZON_SERVER_PORT=8081 \
+ HORIZON_STATIC_DIR=/app/static \
+ HORIZON_CONFIG=/app/horizon.yaml \
+ HORIZON_AUDIT_FILE=/data/horizon-audit.jsonl \
+ HORIZON_SETUP_FILE=/data/horizon-setup.json \
+ HORIZON_ALARMS_FILE=/data/horizon-alarms.json \
+ HORIZON_WIRE_LOG_FILE=/data/horizon-wire.jsonl
+
+USER horizon
+EXPOSE 8081
+CMD ["node", "server.js"]
diff --git a/scripts/.finalize-work/src/HEADER b/scripts/.finalize-work/src/HEADER
new file mode 100644
index 0000000..1745cfe
--- /dev/null
+++ b/scripts/.finalize-work/src/HEADER
@@ -0,0 +1,14 @@
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/scripts/.finalize-work/src/LICENSE b/scripts/.finalize-work/src/LICENSE
new file mode 100644
index 0000000..b512f05
--- /dev/null
+++ b/scripts/.finalize-work/src/LICENSE
@@ -0,0 +1,233 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+=======================================================================
+Apache SkyWalking Subcomponents:
+
+The Apache SkyWalking project contains subcomponents with separate copyright
+notices and license terms. Your use of the source code for the these
+subcomponents is subject to the terms and conditions of the following
+licenses.
+
+========================================================================
+Apache 2.0 licenses
+========================================================================
+
+The following components are provided under the Apache License. See project link for details.
+The text of each license is the standard Apache 2.0 license.
+
+ proto files from cilium: https://github.com/cilium/cilium/tree/v1.15.6/api/v1 Apache 2.0
+ proto files from cncf/udpa: https://github.com/cncf/udpa Apache 2.0
+ proto files from envoyproxy/data-plane-api: https://github.com/envoyproxy/data-plane-api Apache 2.0
+ proto files from prometheus/client_model: https://github.com/prometheus/client_model Apache 2.0
+ proto files from opentelemetry: https://github.com/open-telemetry/opentelemetry-proto/tree/main/opentelemetry/proto Apache 2.0
+ proto files from opentelemetry: https://github.com/open-telemetry/opentelemetry-proto/tree/v0.7.0 Apache 2.0
+ flatbuffers files from istio/proxy: https://github.com/istio/proxy Apache 2.0
+ mvnw files from https://github.com/apache/maven-wrapper Apache 2.0
+ svg files from skywalking-ui/src/assets/icons: https://github.com/google/material-design-icons Apache 2.0
+ ZipkinQueryHandler.java reference from zipkin2.server.internal.ZipkinQueryApiV2: https://github.com/openzipkin/zipkin Apache 2.0
+ Arguments.java in server-library/library-async-profiler-jfr-parser reference from https://github.com/async-profiler/async-profiler/tree/master Apache 2.0
+ CallStack.java in server-library/library-async-profiler-jfr-parser reference from https://github.com/async-profiler/async-profiler/tree/master Apache 2.0
+ Classifier.java in server-library/library-async-profiler-jfr-parser reference from https://github.com/async-profiler/async-profiler/tree/master Apache 2.0
+ Index.java in server-library/library-async-profiler-jfr-parser reference from https://github.com/async-profiler/async-profiler/tree/master Apache 2.0
+ Frame.java in server-library/library-async-profiler-jfr-parser reference from https://github.com/async-profiler/async-profiler/tree/master Apache 2.0
+ JFRConverter.java in server-library/library-async-profiler-jfr-parser reference from https://github.com/async-profiler/async-profiler/tree/master Apache 2.0
\ No newline at end of file
diff --git a/scripts/.finalize-work/src/NOTICE b/scripts/.finalize-work/src/NOTICE
new file mode 100644
index 0000000..ba02a83
--- /dev/null
+++ b/scripts/.finalize-work/src/NOTICE
@@ -0,0 +1,5 @@
+Apache SkyWalking Horizon UI
+Copyright 2017-2026 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/scripts/.finalize-work/src/README.md b/scripts/.finalize-work/src/README.md
new file mode 100644
index 0000000..0bfa0a7
--- /dev/null
+++ b/scripts/.finalize-work/src/README.md
@@ -0,0 +1,58 @@
+Apache SkyWalking Horizon UI
+============================
+
+<img src="https://skywalking.apache.org/assets/logo.svg" alt="SkyWalking logo" height="90px" align="right"/>
+
+The next-generation web UI for [Apache SkyWalking](https://github.com/apache/skywalking). Horizon UI talks to the same OAP GraphQL query-protocol and admin host as the existing [skywalking-booster-ui](https://github.com/apache/skywalking-booster-ui).
+
+[](https://www.apache.org/licenses/LICENSE-2.0)
+
+## Documentation
+
+Full docs ship at [skywalking.apache.org → Horizon UI](https://skywalking.apache.org/docs/) and live in this repo under [`docs/`](docs/README.md). The left-side menu there is the canonical entry point — setup, compatibility, access control, customization, components, and operate are all covered.
+
+## At a glance
+
+- **Multi-layer navigation** — Layers in the sidebar with per-layer service counts, drill-down to overview / services / instances / endpoints / topology, per-layer entity-slot renaming.
+- **Landing** — global cross-layer overview + per-layer landing with service constellation and traffic-share bars.
+- **Service deep-dive** — multi-layer aware; one service can report through General + Service Mesh + K8s at once with side-by-side comparison.
+- **Topology** — force-directed / hierarchical DAG / hex honeycomb variants on the same data.
+- **Endpoint / API dependency** — multi-hop caller → endpoint → downstream view with latency breakdown.
+- **Dashboards** — draggable grid + MQE query drawer + per-scope templates (Glance / Service / Instance / Endpoint / Topology / Metric drill), synced crosshair + compare cursor across panels.
+- **Trace explorer** — duration scatter, sortable list, waterfall with per-span service / kind / error highlighting.
+- **Log explorer** — chip filters, saved views, facets sidebar, timeline brush, mixed JSON / YAML / raw payload tree, top patterns roll-up.
+- **Alarms** — active alarms only, read-only (no acknowledge / close — recovery is backend-driven), with a Live Debug panel for MQE expressions.
+- **Admin** — local + LDAP auth, RBAC (4 built-in roles, 28 verbs), audit log, layer admin, system / OAP cluster health, 5 bundled themes (Horizon · Meridian · Obsidian · Daybreak · Aurora).
+- **Profiling** — flame graphs over trace-based / async-profiler / eBPF / pprof data.
+
+## Tech stack
+
+Vue 3 + TypeScript on Vite 5, Pinia, vue-router 4, Apache ECharts 6, D3 v7, vue-grid-layout, d3-flame-graph, Monaco, Vitest. The BFF is Fastify on Node 20+.
+
+## Development
+
+Requires Node.js 20+ and pnpm 10+ (pinned via Corepack).
+
+```bash
+pnpm install # one-time / after lockfile changes
+pnpm --filter bff dev # BFF on :8081 (NODE_ENV=development)
+pnpm --filter ui dev # Vite dev server on :9091, proxies /api → BFF
+
+pnpm -r type-check
+pnpm -r lint # `lint:fix` mutates
+pnpm -r test:unit
+pnpm license:check # CI gate via skywalking-eyes
+```
+
+Container build (multi-stage — the Dockerfile builds `dist/` from source inside the image, no host pre-step):
+
+```bash
+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`](docs/setup/container-image.md) for image tags, env vars, mounting `horizon.yaml`, and a Kubernetes example.
+
+## License
+
+Apache 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE). License headers and dependency licenses are enforced in CI via [skywalking-eyes](https://github.com/apache/skywalking-eyes); configuration in [.licenserc.yaml](.licenserc.yaml).
diff --git a/scripts/.finalize-work/src/apps/bff/eslint.config.mjs b/scripts/.finalize-work/src/apps/bff/eslint.config.mjs
new file mode 100644
index 0000000..42a8779
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/eslint.config.mjs
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Flat config for ESLint 9. BFF-only: pure TypeScript, no Vue/JSX.
+import js from '@eslint/js';
+import tseslint from 'typescript-eslint';
+
+export default tseslint.config(
+ {
+ ignores: ['dist/**', 'node_modules/**', 'coverage/**', '*.cjs'],
+ },
+ js.configs.recommended,
+ ...tseslint.configs.recommended,
+ {
+ rules: {
+ // The codebase is `strict: true` already (tsc enforces); a few
+ // ergonomics rules off so the lint job stays advisory rather than
+ // mass-rewriting code on first run.
+ '@typescript-eslint/no-explicit-any': 'warn',
+ '@typescript-eslint/no-unused-vars': [
+ 'warn',
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
+ ],
+ '@typescript-eslint/no-empty-object-type': 'off',
+ },
+ },
+);
diff --git a/scripts/.finalize-work/src/apps/bff/package.json b/scripts/.finalize-work/src/apps/bff/package.json
new file mode 100644
index 0000000..e247dd6
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@skywalking-horizon-ui/bff",
+ "version": "0.5.0",
+ "private": true,
+ "type": "module",
+ "main": "dist/server.js",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "dev": "NODE_ENV=development tsx watch src/server.ts",
+ "build": "esbuild src/server.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/server.js --packages=external",
+ "cli:hash": "tsx src/cli/hash.ts",
+ "type-check": "tsc --noEmit",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "test:unit": "vitest run --root src/",
+ "test:unit:watch": "vitest --root src/"
+ },
+ "dependencies": {
+ "@fastify/cookie": "^11.0.1",
+ "@fastify/static": "^9.1.1",
+ "@skywalking-horizon-ui/api-client": "workspace:*",
+ "argon2": "^0.41.1",
+ "chokidar": "^4.0.1",
+ "fastify": "^5.1.0",
+ "ldapts": "^8.1.7",
+ "pino": "^9.5.0",
+ "pino-pretty": "^11.3.0",
+ "yaml": "^2.6.0",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.14.0",
+ "@types/node": "^22.9.0",
+ "@typescript-eslint/eslint-plugin": "^8.16.0",
+ "@typescript-eslint/parser": "^8.16.0",
+ "esbuild": "^0.24.0",
+ "eslint": "^9.14.0",
+ "tsx": "^4.19.2",
+ "typescript": "~5.6.3",
+ "typescript-eslint": "^8.16.0",
+ "vitest": "^2.1.4"
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/audit/logger.ts b/scripts/.finalize-work/src/apps/bff/src/audit/logger.ts
new file mode 100644
index 0000000..325c656
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/audit/logger.ts
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createWriteStream, type WriteStream } from 'node:fs';
+import { mkdir } from 'node:fs/promises';
+import { dirname, resolve } from 'node:path';
+import { logger } from '../logger.js';
+
+export interface AuditEvent {
+ ts: string; // ISO-8601
+ actor: string | null;
+ action: string; // e.g. "rule.addOrUpdate", "auth.login", "role.update"
+ /** Optional verb that authorized the action (e.g. "rule:write"). */
+ verb?: string;
+ target?: string;
+ /** Free-form outcome string; common values include "success", "failure",
+ * the OAP `applyStatus` value, or `http_<code>`. */
+ outcome: string;
+ details?: Record<string, unknown>;
+ fromIp?: string;
+ sessionId?: string;
+}
+
+export class AuditLogger {
+ private stream: WriteStream | null = null;
+ private readonly absPath: string;
+
+ constructor(filePath: string) {
+ this.absPath = resolve(filePath);
+ }
+
+ async open(): Promise<void> {
+ await mkdir(dirname(this.absPath), { recursive: true });
+ this.stream = createWriteStream(this.absPath, { flags: 'a' });
+ this.stream.on('error', (err) => logger.error({ err }, 'audit stream error'));
+ }
+
+ record(evt: Omit<AuditEvent, 'ts'>): void {
+ const line: AuditEvent = { ts: new Date().toISOString(), ...evt };
+ if (!this.stream) {
+ logger.warn({ evt: line }, 'audit logged before open()');
+ return;
+ }
+ this.stream.write(JSON.stringify(line) + '\n');
+ }
+
+ async close(): Promise<void> {
+ if (!this.stream) return;
+ await new Promise<void>((resolveDone) => this.stream!.end(() => resolveDone()));
+ this.stream = null;
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/alert/page-setup.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/alert/page-setup.json
new file mode 100644
index 0000000..8661ab9
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/alert/page-setup.json
@@ -0,0 +1,5 @@
+{
+ "pinnedLayers": ["GENERAL", "MESH"],
+ "defaultWindowMs": 1200000,
+ "overviewAlarmsLimit": 200
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/activemq.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/activemq.json
new file mode 100644
index 0000000..74435c9
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/activemq.json
@@ -0,0 +1,419 @@
+{
+ "key": "ACTIVEMQ",
+ "alias": "ActiveMQ",
+ "group": "MQ",
+ "color": "var(--sw-purple)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-activemq-monitoring/",
+ "aliases": {
+ "services": "ActiveMQ clusters",
+ "instances": "Brokers",
+ "endpoints": "Destinations"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "enqueue",
+ "columns": [
+ {
+ "metric": "enqueue",
+ "label": "Enqueue/s",
+ "mqe": "meter_activemq_cluster_enqueue_rate",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "dequeue",
+ "label": "Dequeue/s",
+ "mqe": "meter_activemq_cluster_dequeue_rate",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "load",
+ "label": "System Load",
+ "mqe": "meter_activemq_cluster_system_load_average/10000",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "threads",
+ "label": "Threads",
+ "mqe": "meter_activemq_cluster_thread_count",
+ "aggregation": "avg"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Throughput",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "enqueue",
+ "label": "Enqueue/s",
+ "tip": "Messages enqueued per second.",
+ "mqe": "meter_activemq_cluster_enqueue_rate",
+ "aggregation": "avg"
+ },
+ {
+ "id": "dequeue",
+ "label": "Dequeue/s",
+ "tip": "Messages dequeued per second.",
+ "mqe": "meter_activemq_cluster_dequeue_rate",
+ "aggregation": "avg"
+ },
+ {
+ "id": "expired",
+ "label": "Expired/s",
+ "tip": "Messages that expired before consumption.",
+ "mqe": "meter_activemq_cluster_expired_rate",
+ "aggregation": "avg"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "load",
+ "title": "System Load Average",
+ "type": "line",
+ "expressions": [
+ "meter_activemq_cluster_system_load_average/10000"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "threads",
+ "title": "Thread Count",
+ "type": "line",
+ "expressions": [
+ "meter_activemq_cluster_thread_count"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "heap_used",
+ "title": "Heap Used (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_activemq_cluster_heap_memory_usage_used/1024/1024"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "heap_max",
+ "title": "Heap Max (MB)",
+ "type": "card",
+ "unit": "MB",
+ "expressions": [
+ "latest(aggregate_labels(meter_activemq_cluster_heap_memory_usage_max,sum))/1024/1024"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "enqueue_dequeue",
+ "title": "Enqueue / Dequeue / Dispatch /s",
+ "type": "line",
+ "expressions": [
+ "meter_activemq_cluster_enqueue_rate",
+ "meter_activemq_cluster_dequeue_rate",
+ "meter_activemq_cluster_dispatch_rate"
+ ],
+ "expressionLabels": [
+ "enqueue",
+ "dequeue",
+ "dispatch"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "expired_rate",
+ "title": "Expired /s",
+ "type": "line",
+ "expressions": [
+ "meter_activemq_cluster_expired_rate"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "enqueue_time",
+ "title": "Enqueue Time",
+ "type": "line",
+ "unit": "s",
+ "expressions": [
+ "meter_activemq_cluster_average_enqueue_time/1000",
+ "meter_activemq_cluster_max_enqueue_time/1000"
+ ],
+ "expressionLabels": [
+ "avg",
+ "max"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "gc_counts",
+ "title": "GC Counts (G1+Parallel)",
+ "type": "line",
+ "expressions": [
+ "view_as_seq(meter_activemq_cluster_gc_g1_old_collection_count,meter_activemq_cluster_gc_parallel_old_collection_count)",
+ "view_as_seq(meter_activemq_cluster_gc_g1_young_collection_count,meter_activemq_cluster_gc_parallel_young_collection_count)"
+ ],
+ "expressionLabels": [
+ "old",
+ "young"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "gc_time",
+ "title": "GC Time (ms)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "view_as_seq(meter_activemq_cluster_gc_g1_old_collection_time,meter_activemq_cluster_gc_parallel_old_collection_time)",
+ "view_as_seq(meter_activemq_cluster_gc_g1_young_collection_time,meter_activemq_cluster_gc_parallel_young_collection_time)"
+ ],
+ "expressionLabels": [
+ "old",
+ "young"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ],
+ "instance": [
+ {
+ "id": "connections_card",
+ "title": "Connections",
+ "tip": "Current TCP/JMS connections to this broker.",
+ "type": "card",
+ "unit": "conns",
+ "expressions": [
+ "latest(meter_activemq_broker_current_connections)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "producer_count_card",
+ "title": "Producer Count",
+ "tip": "Active producer sessions on this broker.",
+ "type": "card",
+ "unit": "producers",
+ "expressions": [
+ "latest(aggregate_labels(meter_activemq_broker_current_producer_count,sum))"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "consumer_count_card",
+ "title": "Consumer Count",
+ "tip": "Active consumer sessions on this broker.",
+ "type": "card",
+ "unit": "consumers",
+ "expressions": [
+ "latest(aggregate_labels(meter_activemq_broker_current_consumer_count,sum))"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "uptime_card",
+ "title": "Uptime",
+ "tip": "Broker uptime in hours since the last restart.",
+ "type": "card",
+ "unit": "h",
+ "expressions": [
+ "latest(meter_activemq_broker_uptime)/1000/60/60"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "decimal"
+ },
+ {
+ "id": "connections_line",
+ "title": "Connections (trend)",
+ "type": "line",
+ "unit": "conns",
+ "expressions": [
+ "meter_activemq_broker_current_connections"
+ ],
+ "span": 4,
+ "rowSpan": 2,
+ "format": "int"
+ },
+ {
+ "id": "enqueue_dequeue_line",
+ "title": "Enqueue / Dequeue Count",
+ "tip": "Per-minute enqueue + dequeue across destinations on this broker.",
+ "type": "line",
+ "unit": "/min",
+ "expressions": [
+ "aggregate_labels(meter_activemq_broker_enqueue_count,sum)",
+ "aggregate_labels(meter_activemq_broker_dequeue_count,sum)"
+ ],
+ "expressionLabels": [
+ "enqueue",
+ "dequeue"
+ ],
+ "span": 4,
+ "rowSpan": 2,
+ "format": "int"
+ },
+ {
+ "id": "producer_consumer_inc_line",
+ "title": "Producer / Consumer Increase",
+ "tip": "New producer + consumer sessions opened per minute.",
+ "type": "line",
+ "unit": "/min",
+ "expressions": [
+ "aggregate_labels(meter_activemq_broker_producer_count,sum)",
+ "aggregate_labels(meter_activemq_broker_consumer_count,sum)"
+ ],
+ "expressionLabels": [
+ "new producers",
+ "new consumers"
+ ],
+ "span": 4,
+ "rowSpan": 2,
+ "format": "int"
+ },
+ {
+ "id": "memory_usage_line",
+ "title": "Memory Usage",
+ "tip": "Aggregate memory usage across destinations (MB).",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "aggregate_labels(meter_activemq_broker_memory_usage,sum)/1024/1024"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "memory_limit_line",
+ "title": "Memory Limit",
+ "tip": "Configured memory ceiling across destinations (GB).",
+ "type": "line",
+ "unit": "GB",
+ "expressions": [
+ "aggregate_labels(meter_activemq_broker_memory_limit,sum)/1024/1024/1024"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "message_size_line",
+ "title": "Avg Message Size",
+ "tip": "Average message size across destinations.",
+ "type": "line",
+ "unit": "bytes",
+ "expressions": [
+ "aggregate_labels(meter_activemq_broker_average_message_size,avg)"
+ ],
+ "span": 4,
+ "rowSpan": 2,
+ "format": "int"
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "producer",
+ "title": "Producer Count",
+ "type": "line",
+ "expressions": [
+ "meter_activemq_destination_producer_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "consumer",
+ "title": "Consumer Count",
+ "type": "line",
+ "expressions": [
+ "meter_activemq_destination_consumer_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "queue_size",
+ "title": "Queue Size",
+ "type": "line",
+ "expressions": [
+ "meter_activemq_destination_queue_size"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "memory",
+ "title": "Memory Usage (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_activemq_destination_memory_usage/1024/1024"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "messages",
+ "title": "Message Counts",
+ "type": "line",
+ "expressions": [
+ "meter_activemq_destination_enqueue_count",
+ "meter_activemq_destination_dequeue_count",
+ "meter_activemq_destination_dispatch_count",
+ "meter_activemq_destination_expired_count",
+ "meter_activemq_destination_inflight_count"
+ ],
+ "expressionLabels": [
+ "enqueue",
+ "dequeue",
+ "dispatch",
+ "expired",
+ "inflight"
+ ],
+ "span": 8,
+ "rowSpan": 2
+ },
+ {
+ "id": "enqueue_time",
+ "title": "Enqueue Time (s)",
+ "type": "line",
+ "unit": "s",
+ "expressions": [
+ "meter_activemq_destination_average_enqueue_time/1000",
+ "meter_activemq_destination_max_enqueue_time/1000"
+ ],
+ "expressionLabels": [
+ "avg",
+ "max"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/alipay_mini_program.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/alipay_mini_program.json
new file mode 100644
index 0000000..19da718
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/alipay_mini_program.json
@@ -0,0 +1,250 @@
+{
+ "key": "ALIPAY_MINI_PROGRAM",
+ "alias": "Alipay Mini Program",
+ "group": "Mobile",
+ "color": "var(--sw-cyan)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-alipay-mini-program-monitoring/",
+ "aliases": {
+ "services": "Mini-programs",
+ "instances": "Versions",
+ "endpoints": "Pages"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": false,
+ "traces": true,
+ "logs": true
+ },
+ "layer-header": {
+ "orderBy": "cpm",
+ "columns": [
+ {
+ "metric": "cpm",
+ "label": "Request RPM",
+ "unit": "rpm",
+ "mqe": "meter_alipay_mp_request_cpm",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "launch",
+ "label": "Launch",
+ "unit": "ms",
+ "mqe": "meter_alipay_mp_app_launch_duration",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "firstRender",
+ "label": "First Render",
+ "unit": "ms",
+ "mqe": "meter_alipay_mp_first_render_duration",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "errors",
+ "label": "Errors",
+ "mqe": "meter_alipay_mp_error_count",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Latency & errors",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "request_p95",
+ "label": "Request P95",
+ "tip": "95th-percentile request duration from the mini-program network bridge.",
+ "mqe": "meter_alipay_mp_request_duration_percentile{p='95'}",
+ "unit": "ms",
+ "aggregation": "avg"
+ },
+ {
+ "id": "launch",
+ "label": "Launch",
+ "tip": "Approximate mini-program launch duration.",
+ "mqe": "meter_alipay_mp_app_launch_duration",
+ "unit": "ms",
+ "aggregation": "avg"
+ },
+ {
+ "id": "errors",
+ "label": "Errors",
+ "tip": "Reported error count.",
+ "mqe": "meter_alipay_mp_error_count",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ {
+ "title": "Throughput",
+ "size": "square",
+ "metrics": [
+ {
+ "id": "cpm",
+ "label": "Request RPM",
+ "tip": "Bridge requests per minute.",
+ "mqe": "meter_alipay_mp_request_cpm",
+ "unit": "rpm",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "launch_duration",
+ "title": "App Launch Duration",
+ "tip": "Approximate cold-launch duration from the Alipay container.",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_alipay_mp_app_launch_duration"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "first_render",
+ "title": "First Render Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_alipay_mp_first_render_duration"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "error_count",
+ "title": "Error Count",
+ "type": "line",
+ "expressions": [
+ "meter_alipay_mp_error_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "request_cpm",
+ "title": "Request Load",
+ "type": "line",
+ "unit": "rpm",
+ "expressions": [
+ "meter_alipay_mp_request_cpm"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "request_percentile",
+ "title": "Request Duration Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(meter_alipay_mp_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ],
+ "instance": [
+ {
+ "id": "v_launch",
+ "title": "Launch Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_alipay_mp_instance_app_launch_duration"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "v_first_render",
+ "title": "First Render Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_alipay_mp_instance_first_render_duration"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "v_request_cpm",
+ "title": "Request Load",
+ "type": "line",
+ "unit": "rpm",
+ "expressions": [
+ "meter_alipay_mp_instance_request_cpm"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "v_request_percentile",
+ "title": "Request Duration Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(meter_alipay_mp_instance_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "ep_launch",
+ "title": "Launch Duration on Page",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_alipay_mp_endpoint_app_launch_duration"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ep_first_render",
+ "title": "First Render Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_alipay_mp_endpoint_first_render_duration"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ep_request_cpm",
+ "title": "Request Load",
+ "type": "line",
+ "unit": "rpm",
+ "expressions": [
+ "meter_alipay_mp_endpoint_request_cpm"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ep_request_percentile",
+ "title": "Request Duration Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(meter_alipay_mp_endpoint_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/apisix.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/apisix.json
new file mode 100644
index 0000000..4cd14af
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/apisix.json
@@ -0,0 +1,300 @@
+{
+ "key": "APISIX",
+ "alias": "APISIX",
+ "group": "Gateways",
+ "color": "var(--sw-ok)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-apisix-monitoring/",
+ "aliases": {
+ "services": "APISIX services",
+ "instances": "Nodes",
+ "endpoints": "Routes"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "rps",
+ "columns": [
+ {
+ "metric": "rps",
+ "label": "RPS",
+ "mqe": "aggregate_labels(meter_apisix_sv_http_requests,sum(service_instance_id))",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "status200",
+ "label": "200/s",
+ "mqe": "aggregate_labels(meter_apisix_sv_http_status_matched{code='200'},sum(code))",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "status404",
+ "label": "404/s",
+ "mqe": "aggregate_labels(meter_apisix_sv_http_status_matched{code='404'},sum(code))",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "status503",
+ "label": "503/s",
+ "mqe": "aggregate_labels(meter_apisix_sv_http_status_matched{code='503'},sum(code))",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Throughput",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "rps",
+ "label": "RPS",
+ "tip": "Requests per second matched by APISIX routes.",
+ "mqe": "aggregate_labels(meter_apisix_sv_http_requests,sum(service_instance_id))",
+ "aggregation": "sum"
+ },
+ {
+ "id": "status5xx",
+ "label": "5xx",
+ "tip": "Upstream / gateway error class.",
+ "mqe": "aggregate_labels(meter_apisix_sv_http_status_matched{code='503'},sum(code))",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "rps",
+ "title": "HTTP Request Trend",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_apisix_sv_http_requests,sum(service_instance_id))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "status",
+ "title": "HTTP Status Trend",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_apisix_sv_http_status_matched,sum(code))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency",
+ "title": "HTTP Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aggregate_labels(meter_apisix_sv_http_latency_matched,avg(type,p))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "bandwidth",
+ "title": "HTTP Bandwidth",
+ "type": "line",
+ "unit": "KB",
+ "expressions": [
+ "aggregate_labels(meter_apisix_sv_bandwidth_matched/1024,sum(type))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "conns",
+ "title": "HTTP Connections",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_apisix_sv_http_connections,sum(state))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "status_unmatched",
+ "title": "Non-matched Status Trend",
+ "tip": "Requests with no matching APISIX route.",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_apisix_sv_http_status_unmatched,sum(code))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency_unmatched",
+ "title": "Non-matched Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aggregate_labels(meter_apisix_sv_http_latency_unmatched,avg(type,p))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "bandwidth_unmatched",
+ "title": "Non-matched Bandwidth",
+ "type": "line",
+ "unit": "KB",
+ "expressions": [
+ "aggregate_labels(meter_apisix_sv_bandwidth_unmatched/1024,sum(type))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ],
+ "instance": [
+ {
+ "id": "rps",
+ "title": "HTTP Request Trend",
+ "type": "line",
+ "expressions": [
+ "meter_apisix_instance_http_requests"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "status",
+ "title": "HTTP Status Trend",
+ "type": "line",
+ "expressions": [
+ "meter_apisix_instance_http_status_matched"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency",
+ "title": "HTTP Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_apisix_instance_http_latency_matched"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "bandwidth",
+ "title": "HTTP Bandwidth",
+ "type": "line",
+ "unit": "KB",
+ "expressions": [
+ "meter_apisix_instance_bandwidth_matched/1024"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "conns",
+ "title": "HTTP Connections",
+ "type": "line",
+ "expressions": [
+ "meter_apisix_instance_http_connections"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "shared_dict",
+ "title": "Shared Dict",
+ "tip": "Capacity vs free space.",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_apisix_instance_shared_dict_capacity_bytes/1024/1024",
+ "meter_apisix_instance_shared_dict_free_space_bytes/1024/1024"
+ ],
+ "expressionLabels": [
+ "capacity",
+ "free"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "etcd",
+ "title": "etcd",
+ "type": "line",
+ "expressions": [
+ "meter_apisix_instance_etcd_indexes",
+ "latest(meter_apisix_instance_etcd_reachable)"
+ ],
+ "expressionLabels": [
+ "indexes",
+ "reachable"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "unmatched",
+ "title": "Non-matched Traffic",
+ "tip": "Status + latency + bandwidth for requests with no matching route.",
+ "type": "line",
+ "expressions": [
+ "meter_apisix_instance_http_status_unmatched",
+ "meter_apisix_instance_http_latency_unmatched",
+ "meter_apisix_instance_bandwidth_unmatched/1024"
+ ],
+ "expressionLabels": [
+ "status",
+ "latency (ms)",
+ "bandwidth (KB)"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "status",
+ "title": "HTTP Status Trend",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_apisix_endpoint_http_status,sum(code))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency",
+ "title": "HTTP Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aggregate_labels(meter_apisix_endpoint_http_latency,avg(type,p))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "bandwidth",
+ "title": "HTTP Bandwidth",
+ "type": "line",
+ "unit": "KB",
+ "expressions": [
+ "aggregate_labels(meter_apisix_endpoint_bandwidth/1024,sum(type))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_dynamodb.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_dynamodb.json
new file mode 100644
index 0000000..6010e3c
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_dynamodb.json
@@ -0,0 +1,420 @@
+{
+ "key": "AWS_DYNAMODB",
+ "alias": "AWS DynamoDB",
+ "group": "AWS",
+ "color": "var(--sw-warn)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-aws-dynamodb-monitoring/",
+ "aliases": {
+ "services": "DynamoDB accounts",
+ "endpoints": "Tables"
+ },
+ "components": {
+ "service": true,
+ "instances": false,
+ "endpoints": true,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "throttled",
+ "columns": [
+ {
+ "metric": "throttledR",
+ "label": "Read Throttled",
+ "mqe": "aws_dynamodb_read_throttled_requests",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "throttledW",
+ "label": "Write Throttled",
+ "mqe": "aws_dynamodb_write_throttled_requests",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "errR",
+ "label": "Read Sys Err",
+ "mqe": "aws_dynamodb_read_system_errors",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "errW",
+ "label": "Write Sys Err",
+ "mqe": "aws_dynamodb_write_system_errors",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Throttling",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "throttledR",
+ "label": "Read Throttled",
+ "tip": "Read requests throttled \u2014 capacity exhaustion.",
+ "mqe": "aws_dynamodb_read_throttled_requests",
+ "aggregation": "sum"
+ },
+ {
+ "id": "throttledW",
+ "label": "Write Throttled",
+ "tip": "Write requests throttled.",
+ "mqe": "aws_dynamodb_write_throttled_requests",
+ "aggregation": "sum"
+ },
+ {
+ "id": "userErr",
+ "label": "User Errors",
+ "tip": "Bad-request style user errors.",
+ "mqe": "aws_dynamodb_user_errors",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "throttled",
+ "title": "Throttled Requests",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_read_throttled_requests",
+ "aws_dynamodb_write_throttled_requests"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "throttle_events",
+ "title": "Throttle Events",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_read_throttle_events",
+ "aws_dynamodb_write_throttle_events"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "sys_errors",
+ "title": "System Errors",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_read_system_errors",
+ "aws_dynamodb_write_system_errors"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "user_errors",
+ "title": "User Errors",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_user_errors"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "cond_check_fail",
+ "title": "Conditional Check Failed",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_conditional_check_failed_requests"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "txn_conflict",
+ "title": "Transaction Conflict",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_transaction_conflict"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "read_usage",
+ "title": "Read Capacity (unit/s)",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_provisioned_read_capacity_units",
+ "aws_dynamodb_consumed_write_capacity_units"
+ ],
+ "expressionLabels": [
+ "provisioned",
+ "consumed"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "write_usage",
+ "title": "Write Capacity (unit/s)",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_provisioned_write_capacity_units",
+ "aws_dynamodb_consumed_write_capacity_units"
+ ],
+ "expressionLabels": [
+ "provisioned",
+ "consumed"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency",
+ "title": "Successful Request Latency (ms)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aws_dynamodb_get_successful_request_latency",
+ "aws_dynamodb_put_successful_request_latency",
+ "aws_dynamodb_query_successful_request_latency",
+ "aws_dynamodb_scan_successful_request_latency"
+ ],
+ "expressionLabels": [
+ "get",
+ "put",
+ "query",
+ "scan"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ },
+ {
+ "id": "ttl_deleted",
+ "title": "TTL Deleted Items",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_time_to_live_deleted_item_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "scan_items",
+ "title": "Scan Returned Items",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_scan_returned_item_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "query_items",
+ "title": "Query Returned Items",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_query_returned_item_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "account_max",
+ "title": "Account Max Reads / Writes",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_account_max_table_level_reads",
+ "aws_dynamodb_account_max_table_level_writes",
+ "aws_dynamodb_account_max_reads",
+ "aws_dynamodb_account_max_writes"
+ ],
+ "expressionLabels": [
+ "table-level reads",
+ "table-level writes",
+ "reads",
+ "writes"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "account_util",
+ "title": "Account Capacity Utilization",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "aws_dynamodb_account_provisioned_read_capacity_utilization",
+ "aws_dynamodb_account_provisioned_write_capacity_utilization"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "throttled",
+ "title": "Throttled Requests",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_read_throttled_requests",
+ "aws_dynamodb_endpoint_write_throttled_requests"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "throttle_events",
+ "title": "Throttle Events",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_read_throttle_events",
+ "aws_dynamodb_endpoint_write_throttle_events"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "sys_errors",
+ "title": "System Errors",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_read_system_errors",
+ "aws_dynamodb_endpoint_write_system_errors"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "cond_check",
+ "title": "Conditional Check Failed",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_conditional_check_failed_requests"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "txn_conflict",
+ "title": "Transaction Conflict",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_transaction_conflict"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ttl_deleted",
+ "title": "TTL Deleted Items",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_time_to_live_deleted_item_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "read_usage",
+ "title": "Read Capacity",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_provisioned_read_capacity_units",
+ "aws_dynamodb_endpoint_consumed_read_capacity_units"
+ ],
+ "expressionLabels": [
+ "provisioned",
+ "consumed"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "write_usage",
+ "title": "Write Capacity",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_provisioned_write_capacity_units",
+ "aws_dynamodb_endpoint_consumed_write_capacity_units"
+ ],
+ "expressionLabels": [
+ "provisioned",
+ "consumed"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency",
+ "title": "Successful Request Latency (ms)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aws_dynamodb_endpoint_get_successful_request_latency",
+ "aws_dynamodb_endpoint_put_successful_request_latency",
+ "aws_dynamodb_endpoint_query_successful_request_latency",
+ "aws_dynamodb_endpoint_scan_successful_request_latency"
+ ],
+ "expressionLabels": [
+ "get",
+ "put",
+ "query",
+ "scan"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ },
+ {
+ "id": "scan_items",
+ "title": "Scan Returned Items",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_scan_returned_item_count"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "query_items",
+ "title": "Query Returned Items",
+ "type": "line",
+ "expressions": [
+ "aws_dynamodb_endpoint_query_returned_item_count"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_eks.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_eks.json
new file mode 100644
index 0000000..dccdeb4
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_eks.json
@@ -0,0 +1,339 @@
+{
+ "key": "AWS_EKS",
+ "alias": "AWS EKS",
+ "group": "AWS",
+ "color": "var(--sw-warn)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-aws-eks-monitoring/",
+ "aliases": {
+ "services": "Clusters",
+ "instances": "Nodes",
+ "endpoints": "EKS services"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "nodes",
+ "columns": [
+ {
+ "metric": "nodes",
+ "label": "Nodes",
+ "mqe": "latest(eks_cluster_node_count)",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "failedNodes",
+ "label": "Failed Nodes",
+ "mqe": "latest(eks_cluster_failed_node_count)",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "namespaces",
+ "label": "Namespaces",
+ "mqe": "latest(eks_cluster_namespace_count)",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "services",
+ "label": "Services",
+ "mqe": "latest(eks_cluster_service_count)",
+ "aggregation": "avg"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Resource",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "nodes",
+ "label": "Nodes",
+ "tip": "EKS cluster node count.",
+ "mqe": "latest(eks_cluster_node_count)",
+ "aggregation": "avg"
+ },
+ {
+ "id": "failedNodes",
+ "label": "Failed Nodes",
+ "tip": "Failed EKS nodes reported by the cluster.",
+ "mqe": "latest(eks_cluster_failed_node_count)",
+ "aggregation": "avg"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "node_count",
+ "title": "Node Count",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_node_count"
+ ],
+ "span": 3,
+ "rowSpan": 2,
+ "format": "int"
+ },
+ {
+ "id": "failed_nodes",
+ "title": "Failed Nodes",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_failed_node_count"
+ ],
+ "span": 3,
+ "rowSpan": 2,
+ "format": "int"
+ },
+ {
+ "id": "namespace_count",
+ "title": "Namespace Count",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_namespace_count"
+ ],
+ "span": 3,
+ "rowSpan": 2,
+ "format": "int"
+ },
+ {
+ "id": "service_count",
+ "title": "EKS Service Count",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_service_count"
+ ],
+ "span": 3,
+ "rowSpan": 2,
+ "format": "int"
+ },
+ {
+ "id": "network_errors",
+ "title": "Cluster Network Errors",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_net_rx_error",
+ "eks_cluster_net_tx_error"
+ ],
+ "expressionLabels": [
+ "rx",
+ "tx"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "network_dropped",
+ "title": "Cluster Network Drops",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_net_rx_dropped",
+ "eks_cluster_net_tx_dropped"
+ ],
+ "expressionLabels": [
+ "rx",
+ "tx"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "pod_cpu",
+ "title": "Pod CPU Utilization (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "eks_cluster_service_pod_cpu_utilization"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "pod_mem",
+ "title": "Pod Memory Utilization (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "eks_cluster_service_pod_memory_utilization"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "pod_net_rx",
+ "title": "Pod Network RX (KB/s)",
+ "type": "line",
+ "unit": "KB/s",
+ "expressions": [
+ "eks_cluster_service_pod_net_rx_bytes/1024"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "pod_net_rx_err",
+ "title": "Pod RX Errors / s",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_service_pod_net_rx_error"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "pod_net_tx",
+ "title": "Pod Network TX (KB/s)",
+ "type": "line",
+ "unit": "KB/s",
+ "expressions": [
+ "eks_cluster_service_pod_net_tx_bytes/1024"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "pod_net_tx_err",
+ "title": "Pod TX Errors / s",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_service_pod_net_tx_error"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ],
+ "instance": [
+ {
+ "id": "pods",
+ "title": "Pod Count",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_node_pod_number"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "node_cpu",
+ "title": "CPU Utilization (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "eks_cluster_node_cpu_utilization"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "node_mem",
+ "title": "Memory Utilization (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "eks_cluster_node_memory_utilization"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "node_fs",
+ "title": "FS Utilization (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "eks_cluster_node_fs_utilization"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "node_net_rx",
+ "title": "Network RX (KB/s)",
+ "type": "line",
+ "unit": "KB/s",
+ "expressions": [
+ "eks_cluster_node_net_rx_bytes/1024",
+ "eks_cluster_node_net_rx_error"
+ ],
+ "expressionLabels": [
+ "bytes",
+ "errors"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "node_net_tx",
+ "title": "Network TX (KB/s)",
+ "type": "line",
+ "unit": "KB/s",
+ "expressions": [
+ "eks_cluster_node_net_tx_bytes/1024",
+ "eks_cluster_node_net_tx_error"
+ ],
+ "expressionLabels": [
+ "bytes",
+ "errors"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "disk",
+ "title": "Disk IO (B/s)",
+ "type": "line",
+ "expressions": [
+ "eks_cluster_node_disk_io_read",
+ "eks_cluster_node_disk_io_write"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "pod_cpu",
+ "title": "Pod CPU on Node",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "eks_cluster_node_pod_cpu_utilization"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "pod_mem",
+ "title": "Pod Memory on Node",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "eks_cluster_node_pod_memory_utilization"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_gateway.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_gateway.json
new file mode 100644
index 0000000..301fdf1
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_gateway.json
@@ -0,0 +1,271 @@
+{
+ "key": "AWS_GATEWAY",
+ "alias": "AWS API Gateway",
+ "group": "AWS",
+ "color": "var(--sw-warn)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-aws-api-gateway-monitoring/",
+ "aliases": {
+ "services": "AWS Gateways",
+ "endpoints": "Routes"
+ },
+ "components": {
+ "service": true,
+ "instances": false,
+ "endpoints": true,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "count",
+ "columns": [
+ {
+ "metric": "count",
+ "label": "Requests",
+ "mqe": "aws_gateway_service_count",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "latency",
+ "label": "Latency",
+ "unit": "ms",
+ "mqe": "aws_gateway_service_latency",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "err4xx",
+ "label": "4xx",
+ "mqe": "aws_gateway_service_4xx",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "err5xx",
+ "label": "5xx",
+ "mqe": "aws_gateway_service_5xx",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Errors",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "err4xx",
+ "label": "4xx",
+ "tip": "Client-error count from CloudWatch.",
+ "mqe": "aws_gateway_service_4xx",
+ "aggregation": "sum"
+ },
+ {
+ "id": "err5xx",
+ "label": "5xx",
+ "tip": "Server-error count from CloudWatch.",
+ "mqe": "aws_gateway_service_5xx",
+ "aggregation": "sum"
+ },
+ {
+ "id": "latency",
+ "label": "Avg Latency",
+ "tip": "Average end-to-end latency.",
+ "mqe": "aws_gateway_service_latency",
+ "unit": "ms",
+ "aggregation": "avg"
+ }
+ ]
+ },
+ {
+ "title": "Throughput",
+ "size": "square",
+ "metrics": [
+ {
+ "id": "count",
+ "label": "Requests",
+ "tip": "Total request count in the window.",
+ "mqe": "aws_gateway_service_count",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "count",
+ "title": "Request Count",
+ "type": "line",
+ "expressions": [
+ "aws_gateway_service_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "err4xx",
+ "title": "4xx Count",
+ "type": "line",
+ "expressions": [
+ "aws_gateway_service_4xx"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "err5xx",
+ "title": "5xx Count",
+ "type": "line",
+ "expressions": [
+ "aws_gateway_service_5xx"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency",
+ "title": "Request Avg Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aws_gateway_service_latency"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "integration_latency",
+ "title": "Integration Avg Latency",
+ "tip": "Latency between the gateway and the backend integration.",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aws_gateway_service_integration_latency"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "data",
+ "title": "Data Processed (HTTP API only)",
+ "type": "line",
+ "unit": "KB",
+ "expressions": [
+ "aws_gateway_service_data_processed/1024"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "cache_hit",
+ "title": "Cache Hit Rate (REST API only)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "aws_gateway_service_cache_hit_rate"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "cache_miss",
+ "title": "Cache Miss Rate (REST API only)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "aws_gateway_service_cache_miss_rate"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "count",
+ "title": "Request Count",
+ "type": "line",
+ "expressions": [
+ "aws_gateway_endpoint_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "err4xx",
+ "title": "4xx Count",
+ "type": "line",
+ "expressions": [
+ "aws_gateway_endpoint_4xx"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "err5xx",
+ "title": "5xx Count",
+ "type": "line",
+ "expressions": [
+ "aws_gateway_endpoint_5xx"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency",
+ "title": "Request Avg Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aws_gateway_endpoint_latency"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "integration_latency",
+ "title": "Integration Avg Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aws_gateway_endpoint_integration_latency"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "data",
+ "title": "Data Processed",
+ "type": "line",
+ "unit": "KB",
+ "expressions": [
+ "aws_gateway_endpoint_DataProcessed/1024"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "cache_hit",
+ "title": "Cache Hit Rate",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "aws_gateway_endpoint_cache_hit_rate"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "cache_miss",
+ "title": "Cache Miss Rate",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "aws_gateway_endpoint_cache_miss_rate"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_s3.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_s3.json
new file mode 100644
index 0000000..86f1b29
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/aws_s3.json
@@ -0,0 +1,188 @@
+{
+ "key": "AWS_S3",
+ "alias": "AWS S3",
+ "group": "AWS",
+ "color": "var(--sw-warn)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-aws-s3-monitoring/",
+ "aliases": {
+ "services": "S3 buckets"
+ },
+ "components": {
+ "service": true,
+ "instances": false,
+ "endpoints": false,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "all",
+ "columns": [
+ {
+ "metric": "all",
+ "label": "Requests",
+ "mqe": "latest(aws_s3_all_requests)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "latency",
+ "label": "Avg Latency",
+ "unit": "ms",
+ "mqe": "avg(aws_s3_request_latency)",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "err4xx",
+ "label": "4xx",
+ "mqe": "latest(aws_s3_4xx)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "err5xx",
+ "label": "5xx",
+ "mqe": "latest(aws_s3_5xx)",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Errors",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "err4xx",
+ "label": "4xx Count",
+ "tip": "Client errors observed by CloudWatch.",
+ "mqe": "latest(aws_s3_4xx)",
+ "aggregation": "sum"
+ },
+ {
+ "id": "err5xx",
+ "label": "5xx Count",
+ "tip": "Server errors observed by CloudWatch.",
+ "mqe": "latest(aws_s3_5xx)",
+ "aggregation": "sum"
+ },
+ {
+ "id": "latency",
+ "label": "Avg Latency",
+ "tip": "Average request latency.",
+ "mqe": "avg(aws_s3_request_latency)",
+ "unit": "ms",
+ "aggregation": "avg"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "all_req",
+ "title": "All Request Count",
+ "type": "line",
+ "expressions": [
+ "aws_s3_all_requests"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "get_req",
+ "title": "GET Request Count",
+ "type": "line",
+ "expressions": [
+ "aws_s3_get_requests"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "put_req",
+ "title": "PUT Request Count",
+ "type": "line",
+ "expressions": [
+ "aws_s3_put_requests"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "delete_req",
+ "title": "DELETE Request Count",
+ "type": "line",
+ "expressions": [
+ "aws_s3_delete_requests"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "err4xx",
+ "title": "4xx Count",
+ "type": "line",
+ "expressions": [
+ "aws_s3_4xx"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "err5xx",
+ "title": "5xx Count",
+ "type": "line",
+ "expressions": [
+ "aws_s3_5xx"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency",
+ "title": "Request Avg Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aws_s3_request_latency"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "first_byte_latency",
+ "title": "First Byte Avg Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aws_s3_first_latency_bytes"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "downloaded",
+ "title": "Downloaded (KB)",
+ "type": "line",
+ "unit": "KB",
+ "expressions": [
+ "aws_s3_downloaded_bytes"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "uploaded",
+ "title": "Uploaded (KB)",
+ "type": "line",
+ "unit": "KB",
+ "expressions": [
+ "aws_s3_uploaded_bytes"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/bookkeeper.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/bookkeeper.json
new file mode 100644
index 0000000..ac404e8
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/bookkeeper.json
@@ -0,0 +1,287 @@
+{
+ "key": "BOOKKEEPER",
+ "alias": "BookKeeper",
+ "group": "MQ",
+ "color": "var(--sw-purple)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-bookkeeper-monitoring/",
+ "aliases": {
+ "services": "BookKeeper clusters",
+ "instances": "Bookies"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": false,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "ledgers",
+ "columns": [
+ {
+ "metric": "ledgers",
+ "label": "Ledgers",
+ "mqe": "aggregate_labels(meter_bookkeeper_bookie_ledgers_count,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "entries",
+ "label": "Entries",
+ "mqe": "aggregate_labels(meter_bookkeeper_bookie_entries_count,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "writableDirs",
+ "label": "Writable Dirs",
+ "mqe": "aggregate_labels(meter_bookkeeper_bookie_ledger_writable_dirs,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "dirUsage",
+ "label": "Dir Usage",
+ "mqe": "aggregate_labels(meter_bookkeeper_bookie_ledger_dir_data_bookkeeper_ledgers_usage,sum)",
+ "aggregation": "avg"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Throughput",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "writeRate",
+ "label": "Write B/s",
+ "tip": "Bookie write throughput across the cluster.",
+ "mqe": "aggregate_labels(meter_bookkeeper_bookie_write_rate,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "id": "readRate",
+ "label": "Read B/s",
+ "tip": "Bookie read throughput across the cluster.",
+ "mqe": "aggregate_labels(meter_bookkeeper_bookie_read_rate,sum)",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "ledgers",
+ "title": "Bookie Ledgers",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_bookkeeper_bookie_ledgers_count,sum)"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "entries",
+ "title": "Bookie Entries",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_bookkeeper_bookie_entries_count,sum)"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "writable_dirs",
+ "title": "Writable Ledger Dirs",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_bookkeeper_bookie_ledger_writable_dirs,sum)"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "write_cache",
+ "title": "Write Cache",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_bookkeeper_bookie_write_cache_size,sum)/1024/1024",
+ "aggregate_labels(meter_bookkeeper_bookie_write_cache_count,sum)"
+ ],
+ "expressionLabels": [
+ "size (MB)",
+ "entries"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "read_cache",
+ "title": "Read Cache",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_bookkeeper_bookie_read_cache_size,sum)/1024/1024",
+ "aggregate_labels(meter_bookkeeper_bookie_read_cache_count,sum)"
+ ],
+ "expressionLabels": [
+ "size (MB)",
+ "entries"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "io_rates",
+ "title": "Read / Write Rate (B/s)",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_bookkeeper_bookie_read_rate,sum)",
+ "aggregate_labels(meter_bookkeeper_bookie_write_rate,sum)"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "dir_usage",
+ "title": "Ledger Dir Usage",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_bookkeeper_bookie_ledger_dir_data_bookkeeper_ledgers_usage,sum)"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ],
+ "instance": [
+ {
+ "id": "jvm_pool",
+ "title": "JVM Memory Pool (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_bookkeeper_node_jvm_memory_pool_used/1024/1024"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_mem",
+ "title": "JVM Memory (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_bookkeeper_node_jvm_memory_used/1024/1024",
+ "meter_bookkeeper_node_jvm_memory_committed/1024/1024",
+ "meter_bookkeeper_node_jvm_memory_init/1024/1024"
+ ],
+ "expressionLabels": [
+ "used",
+ "committed",
+ "init"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_threads",
+ "title": "JVM Threads",
+ "type": "line",
+ "expressions": [
+ "meter_bookkeeper_node_jvm_threads_current",
+ "meter_bookkeeper_node_jvm_threads_daemon",
+ "meter_bookkeeper_node_jvm_threads_peak",
+ "meter_bookkeeper_node_jvm_threads_deadlocked"
+ ],
+ "expressionLabels": [
+ "current",
+ "daemon",
+ "peak",
+ "deadlocked"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "gc",
+ "title": "GC",
+ "type": "line",
+ "expressions": [
+ "meter_bookkeeper_node_jvm_gc_collection_seconds_sum",
+ "meter_bookkeeper_node_jvm_gc_collection_seconds_count"
+ ],
+ "expressionLabels": [
+ "seconds",
+ "count"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "thread_executor",
+ "title": "Thread Executor",
+ "type": "line",
+ "expressions": [
+ "meter_bookkeeper_node_thread_executor_completed",
+ "meter_bookkeeper_node_thread_executor_tasks_completed",
+ "meter_bookkeeper_node_thread_executor_tasks_rejected",
+ "meter_bookkeeper_node_thread_executor_tasks_failed"
+ ],
+ "expressionLabels": [
+ "completed",
+ "tasks completed",
+ "rejected",
+ "failed"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "pooled_threads",
+ "title": "Pooled Threads",
+ "type": "line",
+ "expressions": [
+ "meter_bookkeeper_node_high_priority_threads",
+ "meter_bookkeeper_node_read_thread_pool_threads"
+ ],
+ "expressionLabels": [
+ "high-priority",
+ "read"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "queue_size",
+ "title": "Pool Max Queue Size",
+ "type": "line",
+ "expressions": [
+ "meter_bookkeeper_node_high_priority_thread_max_queue_size",
+ "meter_bookkeeper_node_read_thread_pool_max_queue_size"
+ ],
+ "expressionLabels": [
+ "high-priority",
+ "read"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/browser.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/browser.json
new file mode 100644
index 0000000..dc58364
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/browser.json
@@ -0,0 +1,243 @@
+{
+ "key": "BROWSER",
+ "alias": "Browser",
+ "color": "var(--sw-cyan)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/service-agent/browser-agent/",
+ "aliases": {
+ "services": "Apps",
+ "instances": "Versions",
+ "endpoints": "Pages"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": false,
+ "traces": true,
+ "logs": true
+ },
+ "layer-header": {
+ "orderBy": "pv",
+ "columns": [
+ { "metric": "pv", "label": "Page Views", "unit": "/min", "mqe": "browser_app_pv", "aggregation": "sum" },
+ { "metric": "errorRate", "label": "Error Rate", "unit": "%", "mqe": "browser_app_error_rate/100", "aggregation": "avg" },
+ { "metric": "errorSum", "label": "Errors", "mqe": "browser_app_error_sum", "aggregation": "sum" }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Page health",
+ "size": "auto",
+ "metrics": [
+ { "id": "errorRate", "label": "Error Rate", "tip": "Percentage of page loads that produced an error.", "mqe": "browser_app_error_rate/100", "unit": "%", "aggregation": "avg" }
+ ]
+ },
+ {
+ "title": "Throughput",
+ "size": "square",
+ "metrics": [
+ { "id": "pv", "label": "PV / min", "tip": "Page views per minute.", "mqe": "browser_app_pv", "unit": "/min", "aggregation": "sum" }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "app_pv",
+ "title": "App Load (PV)",
+ "type": "line",
+ "unit": "/min",
+ "expressions": ["browser_app_pv"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "app_error_rate",
+ "title": "App Error Rate",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["browser_app_error_rate/100"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "app_error_sum",
+ "title": "App Error Count",
+ "type": "line",
+ "expressions": ["browser_app_error_sum"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "top_pages_pv",
+ "title": "Top Hot Pages",
+ "tip": "Pages ranked by page views, errors, and error rate.",
+ "type": "top",
+ "expressions": [
+ "top_n(browser_app_page_pv,10,des)",
+ "top_n(browser_app_page_error_sum,10,des)",
+ "top_n(browser_app_page_error_rate,10,des)/100"
+ ],
+ "expressionLabels": ["PV", "Errors", "Error Rate"],
+ "expressionUnits": ["/min", "", "%"],
+ "span": 6,
+ "rowSpan": 3
+ },
+ {
+ "id": "top_versions",
+ "title": "Top Versions",
+ "tip": "App version breakdown — load + errors + error rate.",
+ "type": "top",
+ "expressions": [
+ "top_n(browser_app_single_version_pv,10,des)",
+ "top_n(browser_app_single_version_error_sum,10,des)",
+ "top_n(browser_app_single_version_error_rate,10,des)/100"
+ ],
+ "expressionLabels": ["PV", "Errors", "Error Rate"],
+ "expressionUnits": ["/min", "", "%"],
+ "span": 6,
+ "rowSpan": 3
+ }
+ ],
+ "instance": [
+ {
+ "id": "ver_pv",
+ "title": "Version PV",
+ "type": "line",
+ "unit": "/min",
+ "expressions": ["browser_app_single_version_pv"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ver_error_rate",
+ "title": "Version Error Rate",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["browser_app_single_version_error_rate/100"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ver_error_sum",
+ "title": "Version Error Count",
+ "type": "line",
+ "expressions": ["browser_app_single_version_error_sum"],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "page_fmp_percentile",
+ "title": "First Meaningful Paint Percentile",
+ "tip": "p50–p99 of FMP latency for the selected page. Below 1s for p75 is a common target.",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(browser_app_page_fmp_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "page_load_percentile",
+ "title": "Page Load Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(browser_app_page_load_page_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "page_ttl_percentile",
+ "title": "Time-to-Live Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(browser_app_page_ttl_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "page_first_pack_percentile",
+ "title": "First Pack Latency Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(browser_app_page_first_pack_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "page_performance",
+ "title": "Page Performance Breakdown",
+ "tip": "Avg time spent in each phase of page load — resolve, redirect, DNS, TCP, TTFB, transfer, DOM analysis, DOM ready, FPT, full load.",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "browser_app_page_dns_avg",
+ "browser_app_page_redirect_avg",
+ "browser_app_page_tcp_avg",
+ "browser_app_page_ttfb_avg",
+ "browser_app_page_trans_avg",
+ "browser_app_page_dom_analysis_avg",
+ "browser_app_page_dom_ready_avg",
+ "browser_app_page_fpt_avg",
+ "browser_app_page_load_page_avg",
+ "browser_app_page_res_avg"
+ ],
+ "expressionLabels": ["dns", "redirect", "tcp", "ttfb", "transfer", "dom analysis", "dom ready", "fpt", "load", "resource"],
+ "span": 12,
+ "rowSpan": 3
+ },
+ {
+ "id": "page_error_breakdown",
+ "title": "Page Errors by Type",
+ "tip": "Resource / JS / AJAX / unknown error counters per minute.",
+ "type": "line",
+ "expressions": [
+ "browser_app_page_resource_error_sum",
+ "browser_app_page_js_error_sum",
+ "browser_app_page_ajax_error_sum",
+ "browser_app_page_unknown_error_sum"
+ ],
+ "expressionLabels": ["resource", "js", "ajax", "unknown"],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "web_vitals",
+ "title": "Web Vitals",
+ "tip": "Core Web Vitals — FMP, LCP, CLS (avg per minute). CLS is divided by 1000 to bring it into the typical 0–1 score range.",
+ "type": "line",
+ "expressions": [
+ "browser_app_web_vitals_fmp_avg",
+ "browser_app_web_vitals_lcp_avg",
+ "browser_app_web_vitals_cls_avg / 1000"
+ ],
+ "expressionLabels": ["FMP (ms)", "LCP (ms)", "CLS"],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "inp_percentile",
+ "title": "Interaction to Next Paint Percentile",
+ "tip": "INP — responsiveness metric replacing FID.",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(browser_app_web_interaction_inp_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/cilium_service.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/cilium_service.json
new file mode 100644
index 0000000..e4a42d3
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/cilium_service.json
@@ -0,0 +1,487 @@
+{
+ "key": "CILIUM_SERVICE",
+ "alias": "Cilium Service",
+ "group": "Kubernetes",
+ "color": "var(--sw-info)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-k8s-monitoring-cilium/",
+ "aliases": {
+ "services": "Services",
+ "instances": "Pods",
+ "endpoints": "Endpoints"
+ },
+ "naming": {
+ "pattern": "^(?<service>[^.]+)\\.(?<namespace>[^.]+)(?:\\..*)?$",
+ "displayGroup": "service",
+ "valueGroup": "namespace",
+ "alias": "namespace"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": true,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "cpm",
+ "columns": [
+ {
+ "metric": "cpm",
+ "label": "RPM",
+ "unit": "rpm",
+ "mqe": "cilium_service_protocol_cpm",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "respTime",
+ "label": "Latency",
+ "unit": "ms",
+ "mqe": "cilium_service_protocol_call_duration/cilium_service_protocol_cpm/1000000",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "sla",
+ "label": "Success Rate",
+ "unit": "%",
+ "mqe": "cilium_service_protocol_call_success_count/cilium_service_protocol_cpm*100",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "drop",
+ "label": "TCP Drop",
+ "mqe": "cilium_service_l4_read_pkg_drop_cpm + cilium_service_l4_write_pkg_drop_cpm",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Latency & errors",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "respTime",
+ "label": "Avg Latency",
+ "tip": "Average protocol latency (ms).",
+ "mqe": "cilium_service_protocol_call_duration/cilium_service_protocol_cpm/1000000",
+ "unit": "ms",
+ "aggregation": "avg"
+ },
+ {
+ "id": "successRate",
+ "label": "Success Rate",
+ "tip": "Successful calls / total calls.",
+ "mqe": "cilium_service_protocol_call_success_count/cilium_service_protocol_cpm*100",
+ "unit": "%",
+ "aggregation": "avg"
+ }
+ ]
+ },
+ {
+ "title": "Throughput",
+ "size": "square",
+ "metrics": [
+ {
+ "id": "cpm",
+ "label": "Protocol RPM",
+ "tip": "Protocol calls per minute.",
+ "mqe": "cilium_service_protocol_cpm",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "l4_read",
+ "title": "L4 Read Packages/min",
+ "type": "line",
+ "expressions": [
+ "cilium_service_l4_read_pkg_cpm"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "l4_write",
+ "title": "L4 Write Packages/min",
+ "type": "line",
+ "expressions": [
+ "cilium_service_l4_write_pkg_cpm"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "tcp_drop",
+ "title": "TCP Drop / min",
+ "type": "line",
+ "expressions": [
+ "cilium_service_l4_read_pkg_drop_cpm",
+ "cilium_service_l4_write_pkg_drop_cpm"
+ ],
+ "expressionLabels": [
+ "read",
+ "write"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "tcp_drop_reason",
+ "title": "TCP Drop by Reason",
+ "type": "line",
+ "expressions": [
+ "cilium_service_l4_drop_reason_count"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "http_load",
+ "title": "HTTP Load",
+ "type": "line",
+ "expressions": [
+ "cilium_service_protocol_http_call_cpm",
+ "cilium_service_protocol_http_call_success_count"
+ ],
+ "expressionLabels": [
+ "calls",
+ "success"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "http_duration",
+ "title": "HTTP Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "cilium_service_protocol_http_call_duration/cilium_service_protocol_http_call_cpm/1000000"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "http_status",
+ "title": "HTTP Non-2xx Status",
+ "type": "line",
+ "expressions": [
+ "cilium_service_protocol_http_status_1xx_cpm",
+ "cilium_service_protocol_http_status_3xx_cpm",
+ "cilium_service_protocol_http_status_4xx_cpm",
+ "cilium_service_protocol_http_status_5xx_cpm"
+ ],
+ "expressionLabels": [
+ "1xx",
+ "3xx",
+ "4xx",
+ "5xx"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "dns_load",
+ "title": "DNS Load",
+ "type": "line",
+ "expressions": [
+ "cilium_service_protocol_dns_call_cpm",
+ "cilium_service_protocol_dns_call_success_count"
+ ],
+ "expressionLabels": [
+ "calls",
+ "success"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "dns_duration",
+ "title": "DNS Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "cilium_service_protocol_dns_call_duration/cilium_service_protocol_dns_call_cpm/1000000"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "dns_error",
+ "title": "DNS Errors / min",
+ "type": "line",
+ "expressions": [
+ "cilium_service_protocol_dns_error_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "kafka_load",
+ "title": "Kafka Load",
+ "type": "line",
+ "expressions": [
+ "cilium_service_protocol_kafka_call_cpm",
+ "cilium_service_protocol_kafka_call_success_count"
+ ],
+ "expressionLabels": [
+ "calls",
+ "success"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "kafka_duration",
+ "title": "Kafka Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "cilium_service_protocol_kafka_call_duration/cilium_service_protocol_kafka_call_cpm/1000000"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "kafka_error",
+ "title": "Kafka Errors / min",
+ "type": "line",
+ "expressions": [
+ "cilium_service_protocol_kafka_call_error_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ],
+ "instance": [
+ {
+ "id": "l4_read",
+ "title": "L4 Read Packages/min",
+ "type": "line",
+ "expressions": [
+ "cilium_service_instance_l4_read_pkg_cpm"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "l4_write",
+ "title": "L4 Write Packages/min",
+ "type": "line",
+ "expressions": [
+ "cilium_service_instance_l4_write_pkg_cpm"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "http_load",
+ "title": "HTTP Load",
+ "type": "line",
+ "expressions": [
+ "cilium_service_instance_protocol_http_call_cpm",
+ "cilium_service_instance_protocol_http_call_success_count"
+ ],
+ "expressionLabels": [
+ "calls",
+ "success"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "http_status",
+ "title": "HTTP Non-2xx Status",
+ "type": "line",
+ "expressions": [
+ "cilium_service_instance_protocol_http_status_1xx_cpm",
+ "cilium_service_instance_protocol_http_status_3xx_cpm",
+ "cilium_service_instance_protocol_http_status_4xx_cpm",
+ "cilium_service_instance_protocol_http_status_5xx_cpm"
+ ],
+ "expressionLabels": [
+ "1xx",
+ "3xx",
+ "4xx",
+ "5xx"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "dns_load",
+ "title": "DNS Load",
+ "type": "line",
+ "expressions": [
+ "cilium_service_instance_protocol_dns_call_cpm",
+ "cilium_service_instance_protocol_dns_call_success_count"
+ ],
+ "expressionLabels": [
+ "calls",
+ "success"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "dns_error",
+ "title": "DNS Errors",
+ "type": "line",
+ "expressions": [
+ "cilium_service_instance_protocol_dns_error_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "kafka_load",
+ "title": "Kafka Load",
+ "type": "line",
+ "expressions": [
+ "cilium_service_instance_protocol_kafka_call_cpm",
+ "cilium_service_instance_protocol_kafka_call_success_count"
+ ],
+ "expressionLabels": [
+ "calls",
+ "success"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "http_load",
+ "title": "HTTP Load",
+ "type": "line",
+ "expressions": [
+ "cilium_endpoint_protocol_http_call_cpm",
+ "cilium_endpoint_protocol_http_call_success_count"
+ ],
+ "expressionLabels": [
+ "calls",
+ "success"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "http_duration",
+ "title": "HTTP Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "cilium_endpoint_protocol_http_call_duration/cilium_endpoint_protocol_http_call_cpm/1000000"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "http_status",
+ "title": "HTTP Non-2xx Status",
+ "type": "line",
+ "expressions": [
+ "cilium_endpoint_protocol_http_status_1xx_cpm",
+ "cilium_endpoint_protocol_http_status_3xx_cpm",
+ "cilium_endpoint_protocol_http_status_4xx_cpm",
+ "cilium_endpoint_protocol_http_status_5xx_cpm"
+ ],
+ "expressionLabels": [
+ "1xx",
+ "3xx",
+ "4xx",
+ "5xx"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "dns_load",
+ "title": "DNS Load",
+ "type": "line",
+ "expressions": [
+ "cilium_endpoint_protocol_dns_call_cpm",
+ "cilium_endpoint_protocol_dns_call_success_count"
+ ],
+ "expressionLabels": [
+ "calls",
+ "success"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "dns_duration",
+ "title": "DNS Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "cilium_endpoint_protocol_dns_call_duration/cilium_endpoint_protocol_dns_call_cpm/1000000"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "dns_error",
+ "title": "DNS Errors",
+ "type": "line",
+ "expressions": [
+ "cilium_endpoint_protocol_dns_error_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ]
+ },
+ "topology": {
+ "nodeMetrics": [
+ {
+ "id": "cpm",
+ "label": "RPM",
+ "mqe": "cilium_service_protocol_cpm",
+ "unit": "rpm",
+ "role": "center",
+ "aggregation": "avg"
+ },
+ {
+ "id": "success",
+ "label": "Success %",
+ "mqe": "cilium_service_protocol_call_success_count/cilium_service_protocol_cpm*100",
+ "unit": "%",
+ "role": "ring",
+ "aggregation": "avg"
+ },
+ {
+ "id": "respTime",
+ "label": "Latency",
+ "mqe": "cilium_service_protocol_call_duration/cilium_service_protocol_cpm/1000000",
+ "unit": "ms",
+ "role": "secondary",
+ "aggregation": "avg"
+ }
+ ],
+ "linkServerMetrics": [
+ {
+ "id": "cpm",
+ "label": "HTTP RPM",
+ "mqe": "cilium_service_relation_server_protocol_http_call_cpm",
+ "unit": "rpm",
+ "role": "lineServer",
+ "aggregation": "avg"
+ },
+ {
+ "id": "respTime",
+ "label": "Avg Latency",
+ "mqe": "cilium_service_relation_server_protocol_http_call_duration/cilium_service_relation_server_protocol_http_call_cpm/1000000",
+ "unit": "ms",
+ "aggregation": "avg"
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/clickhouse.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/clickhouse.json
new file mode 100644
index 0000000..9bb5f79
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/clickhouse.json
@@ -0,0 +1,475 @@
+{
+ "key": "CLICKHOUSE",
+ "alias": "ClickHouse",
+ "group": "Databases",
+ "color": "var(--sw-cyan)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-clickhouse-monitoring/",
+ "aliases": {
+ "services": "ClickHouse clusters",
+ "instances": "Nodes"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": false,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "selRate",
+ "columns": [
+ {
+ "metric": "selRate",
+ "label": "Select / s",
+ "mqe": "aggregate_labels(meter_clickhouse_query_select_rate,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "insRate",
+ "label": "Insert / s",
+ "mqe": "aggregate_labels(meter_clickhouse_query_insert_rate,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "slowQ",
+ "label": "Slow Reads",
+ "mqe": "aggregate_labels(meter_clickhouse_query_slow,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "fileOpen",
+ "label": "Open Files",
+ "mqe": "latest(aggregate_labels(meter_clickhouse_file_open,sum))",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Throughput",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "selRate",
+ "label": "Select QPS",
+ "tip": "Selects per second across the cluster.",
+ "mqe": "aggregate_labels(meter_clickhouse_query_select_rate,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "id": "insRate",
+ "label": "Insert QPS",
+ "tip": "Inserts per second.",
+ "mqe": "aggregate_labels(meter_clickhouse_query_insert_rate,sum)",
+ "aggregation": "sum"
+ },
+ {
+ "id": "slowQ",
+ "label": "Slow Reads",
+ "tip": "Slow file-reads counter.",
+ "mqe": "aggregate_labels(meter_clickhouse_query_slow,sum)",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "files_open",
+ "title": "Files Open",
+ "type": "card",
+ "expressions": [
+ "latest(aggregate_labels(meter_clickhouse_file_open,sum))"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "qps",
+ "title": "QPS",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_query_select_rate,sum)",
+ "aggregate_labels(meter_clickhouse_query_insert_rate,sum)"
+ ],
+ "expressionLabels": [
+ "select",
+ "insert"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "queries",
+ "title": "Queries",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_query,sum)",
+ "aggregate_labels(meter_clickhouse_query_select,sum)",
+ "aggregate_labels(meter_clickhouse_query_insert,sum)"
+ ],
+ "expressionLabels": [
+ "total",
+ "select",
+ "insert"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "query_time",
+ "title": "Query Time (ms)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_querytime_microseconds,sum)/aggregate_labels(meter_clickhouse_query,sum)/1000",
+ "aggregate_labels(meter_clickhouse_querytime_select_microseconds,sum)/aggregate_labels(meter_clickhouse_query_select,sum)/1000",
+ "aggregate_labels(meter_clickhouse_querytime_insert_microseconds,sum)/aggregate_labels(meter_clickhouse_query_insert,sum)/1000"
+ ],
+ "expressionLabels": [
+ "avg",
+ "select",
+ "insert"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "conns",
+ "title": "Connections",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_tcp_connections,sum)",
+ "aggregate_labels(meter_clickhouse_http_connections,sum)"
+ ],
+ "expressionLabels": [
+ "TCP",
+ "HTTP"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "slow",
+ "title": "Slow Reads",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_query_slow,sum)"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "merge_mut",
+ "title": "Merge / Mutations",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_background_merge,sum)",
+ "aggregate_labels(meter_clickhouse_mutations,sum)"
+ ],
+ "expressionLabels": [
+ "merge",
+ "mutations"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "insert_io",
+ "title": "Insert Throughput",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_inserted_bytes,sum)",
+ "aggregate_labels(meter_clickhouse_inserted_rows,sum)"
+ ],
+ "expressionLabels": [
+ "bytes/s",
+ "rows/s"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "delayed",
+ "title": "Delayed Inserts (s)",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_delayed_inserts,sum)"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "parts_active",
+ "title": "Active Data Parts",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_parts_active,sum)"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "replicated",
+ "title": "Replicated Fetch / Send",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_clickhouse_replicated_fetch,sum)",
+ "aggregate_labels(meter_clickhouse_replicated_send,sum)"
+ ],
+ "expressionLabels": [
+ "fetch",
+ "send"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "zk",
+ "title": "Zookeeper Activity",
+ "type": "line",
+ "expressions": [
+ "latest(aggregate_labels(meter_clickhouse_zookeeper_session,sum))",
+ "latest(aggregate_labels(meter_clickhouse_zookeeper_watch,sum))",
+ "aggregate_labels(meter_clickhouse_zookeeper_bytes_sent,sum)",
+ "aggregate_labels(meter_clickhouse_zookeeper_bytes_received,sum)"
+ ],
+ "expressionLabels": [
+ "sessions",
+ "watches",
+ "bytes sent",
+ "bytes recv"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "keeper_alive",
+ "title": "Keeper Alive Conns",
+ "type": "card",
+ "expressions": [
+ "latest(aggregate_labels(meter_clickhouse_keeper_connections_alive,sum))"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "keeper_outstanding",
+ "title": "Keeper Outstanding Requests",
+ "type": "card",
+ "expressions": [
+ "latest(aggregate_labels(meter_clickhouse_keeper_outstanding_requests,sum))"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ }
+ ],
+ "instance": [
+ {
+ "id": "uptime",
+ "title": "Uptime (days)",
+ "type": "card",
+ "expressions": [
+ "latest(meter_clickhouse_instance_uptime)/3600/24"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "version",
+ "title": "Version",
+ "type": "card",
+ "expressions": [
+ "latest(meter_clickhouse_instance_version)"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "cpu",
+ "title": "CPU (cores)",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_cpu_usage/1000000"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "mem",
+ "title": "Memory (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "meter_clickhouse_instance_memory_usage",
+ "meter_clickhouse_instance_memory_available"
+ ],
+ "expressionLabels": [
+ "used",
+ "available"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "network",
+ "title": "Network (B)",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_network_receive_bytes",
+ "meter_clickhouse_instance_network_send_bytes"
+ ],
+ "expressionLabels": [
+ "receive",
+ "send"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "conns",
+ "title": "Connections",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_tcp_connections",
+ "meter_clickhouse_instance_http_connections"
+ ],
+ "expressionLabels": [
+ "TCP",
+ "HTTP"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "queries",
+ "title": "Queries",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_query",
+ "meter_clickhouse_instance_query_select",
+ "meter_clickhouse_instance_query_insert"
+ ],
+ "expressionLabels": [
+ "total",
+ "select",
+ "insert"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "qps",
+ "title": "QPS",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_query_select_rate",
+ "meter_clickhouse_instance_query_insert_rate"
+ ],
+ "expressionLabels": [
+ "select",
+ "insert"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "query_time",
+ "title": "Query Time (ms)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_clickhouse_instance_querytime_microseconds/meter_clickhouse_instance_query/1000",
+ "meter_clickhouse_instance_querytime_select_microseconds/meter_clickhouse_instance_query_select/1000",
+ "meter_clickhouse_instance_querytime_insert_microseconds/meter_clickhouse_instance_query_insert/1000"
+ ],
+ "expressionLabels": [
+ "avg",
+ "select",
+ "insert"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "slow",
+ "title": "File Slow Read",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_query_slow"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "merge",
+ "title": "Background Merge",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_background_merge"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "mutations",
+ "title": "Mutations",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_mutations"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "files",
+ "title": "Files Open",
+ "type": "card",
+ "expressions": [
+ "latest(meter_clickhouse_instance_file_open)"
+ ],
+ "span": 4,
+ "rowSpan": 1
+ },
+ {
+ "id": "insert_io",
+ "title": "Insert Throughput",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_inserted_bytes",
+ "meter_clickhouse_instance_inserted_rows"
+ ],
+ "expressionLabels": [
+ "bytes/s",
+ "rows/s"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "delayed",
+ "title": "Delayed Inserts (s)",
+ "type": "line",
+ "expressions": [
+ "meter_clickhouse_instance_delayed_inserts"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/elasticsearch.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/elasticsearch.json
new file mode 100644
index 0000000..bd0c844
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/elasticsearch.json
@@ -0,0 +1,491 @@
+{
+ "key": "ELASTICSEARCH",
+ "alias": "Elasticsearch",
+ "group": "Databases",
+ "color": "var(--sw-cyan)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-elasticsearch-monitoring/",
+ "aliases": {
+ "services": "ES clusters",
+ "instances": "Nodes",
+ "endpoints": "Indices"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "shards",
+ "columns": [
+ {
+ "metric": "health",
+ "label": "Health",
+ "mqe": "meter_elasticsearch_cluster_health_status",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "shards",
+ "label": "Shards",
+ "mqe": "latest(meter_elasticsearch_cluster_shards_total)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "nodes",
+ "label": "Nodes",
+ "mqe": "latest(meter_elasticsearch_cluster_nodes)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "unassigned",
+ "label": "Unassigned",
+ "mqe": "latest(meter_elasticsearch_cluster_unassigned_shards_total)",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Cluster",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "health",
+ "label": "Health",
+ "tip": "0 green / 1 yellow / 2 red.",
+ "mqe": "meter_elasticsearch_cluster_health_status",
+ "aggregation": "avg"
+ },
+ {
+ "id": "unassigned",
+ "label": "Unassigned Shards",
+ "tip": "Shards not yet placed on any node.",
+ "mqe": "latest(meter_elasticsearch_cluster_unassigned_shards_total)",
+ "aggregation": "sum"
+ },
+ {
+ "id": "breakers",
+ "label": "Tripped Breakers",
+ "tip": "Cumulative circuit-breaker trips \u2014 high values indicate memory pressure.",
+ "mqe": "latest(meter_elasticsearch_cluster_breakers_tripped)",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "health",
+ "title": "Cluster Health",
+ "type": "table",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_health_status)"
+ ],
+ "span": 4,
+ "rowSpan": 3,
+ "tableHeaders": [
+ "Status",
+ "Value"
+ ],
+ "showTableValues": true
+ },
+ {
+ "id": "nodes",
+ "title": "Nodes",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_nodes)"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "pending_tasks",
+ "title": "Pending Tasks",
+ "type": "card",
+ "expressions": [
+ "avg(meter_elasticsearch_cluster_pending_tasks_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "primary_shards",
+ "title": "Primary Shards",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_primary_shards_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "shards_active",
+ "title": "Active Shards",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_shards_total)"
+ ],
+ "span": 2,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "shards_initializing",
+ "title": "Initializing",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_initializing_shards_total)"
+ ],
+ "span": 2,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "shards_relocating",
+ "title": "Relocating",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_relocating_shards_total)"
+ ],
+ "span": 2,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "shards_unassigned",
+ "title": "Unassigned",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_unassigned_shards_total)"
+ ],
+ "span": 2,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "shards_delayed",
+ "title": "Delayed Unassigned",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_delayed_unassigned_shards_total)"
+ ],
+ "span": 2,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "breakers",
+ "title": "Tripped Breakers",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_cluster_breakers_tripped)"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "cluster_cpu",
+ "title": "Cluster CPU Avg",
+ "type": "card",
+ "unit": "%",
+ "expressions": [
+ "avg(meter_elasticsearch_cluster_cpu_usage_avg)"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "cluster_jvm",
+ "title": "JVM Memory Used Avg",
+ "type": "card",
+ "expressions": [
+ "avg(meter_elasticsearch_cluster_jvm_memory_used_avg)"
+ ],
+ "span": 4,
+ "rowSpan": 1
+ },
+ {
+ "id": "open_files",
+ "title": "Open Files Avg",
+ "type": "card",
+ "expressions": [
+ "avg(meter_elasticsearch_cluster_open_file_count)"
+ ],
+ "span": 4,
+ "rowSpan": 1
+ }
+ ],
+ "instance": [
+ {
+ "id": "cpu",
+ "title": "Process CPU (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "meter_elasticsearch_node_process_cpu_percent"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "os_cpu",
+ "title": "OS CPU (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "meter_elasticsearch_node_os_cpu_percent"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "load_avg",
+ "title": "Load Average",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_node_os_load1",
+ "meter_elasticsearch_node_os_load5",
+ "meter_elasticsearch_node_os_load15"
+ ],
+ "expressionLabels": [
+ "1m",
+ "5m",
+ "15m"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_mem",
+ "title": "JVM Memory (MB)",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_node_jvm_memory_heap_used/1024/1024",
+ "meter_elasticsearch_node_jvm_memory_heap_max/1024/1024",
+ "meter_elasticsearch_node_jvm_memory_nonheap_used/1024/1024"
+ ],
+ "expressionLabels": [
+ "heap used",
+ "heap max",
+ "non-heap"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "gc",
+ "title": "GC",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_node_jvm_gc_count",
+ "meter_elasticsearch_node_jvm_gc_time"
+ ],
+ "expressionLabels": [
+ "count",
+ "time (ms/min)"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "translog",
+ "title": "Translog",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_node_indices_translog_operations",
+ "meter_elasticsearch_node_indices_translog_size/1024/1024"
+ ],
+ "expressionLabels": [
+ "operations",
+ "size (MB)"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "breakers",
+ "title": "Breakers",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_node_breakers_tripped",
+ "meter_elasticsearch_node_breakers_estimated_size/1024/1024"
+ ],
+ "expressionLabels": [
+ "tripped",
+ "estimated (MB)"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "segments",
+ "title": "Segments",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_node_segment_count",
+ "meter_elasticsearch_node_segment_memory"
+ ],
+ "expressionLabels": [
+ "count",
+ "memory (MB)"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "disk",
+ "title": "Disk Usage",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_node_disk_usage/1024/1024/1024",
+ "meter_elasticsearch_node_disk_usage_percent"
+ ],
+ "expressionLabels": [
+ "GB",
+ "%"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "network",
+ "title": "Network",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_node_network_send_bytes",
+ "meter_elasticsearch_node_network_receive_bytes"
+ ],
+ "expressionLabels": [
+ "send",
+ "receive"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "open_files",
+ "title": "Open Files",
+ "type": "card",
+ "expressions": [
+ "avg(meter_elasticsearch_node_open_file_count)"
+ ],
+ "span": 6,
+ "rowSpan": 1
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "indexing_rate",
+ "title": "Indexing Rate",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_index_stats_indexing_index_total_req_rate",
+ "meter_elasticsearch_index_stats_indexing_index_total_proc_rate"
+ ],
+ "expressionLabels": [
+ "req",
+ "proc"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "search_rate",
+ "title": "Search Rate",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_index_stats_search_query_total_req_rate",
+ "meter_elasticsearch_index_stats_search_query_total_proc_rate"
+ ],
+ "expressionLabels": [
+ "req",
+ "proc"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "idx_size_all",
+ "title": "Index Size (all shards)",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_index_indices_store_size_bytes_total)/1024/1024/1024"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "decimal",
+ "unit": "GB"
+ },
+ {
+ "id": "idx_size_primary",
+ "title": "Index Size (primary)",
+ "type": "card",
+ "expressions": [
+ "latest(meter_elasticsearch_index_indices_store_size_bytes_primary)/1024/1024/1024"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "decimal",
+ "unit": "GB"
+ },
+ {
+ "id": "docs",
+ "title": "Documents",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_index_indices_docs_total",
+ "meter_elasticsearch_index_indices_docs_primary",
+ "latest(meter_elasticsearch_index_indices_deleted_docs_primary)"
+ ],
+ "expressionLabels": [
+ "all",
+ "primary",
+ "deleted"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "search_time",
+ "title": "Avg Search Time / Req (s)",
+ "type": "line",
+ "expressions": [
+ "meter_elasticsearch_index_search_fetch_avg_time",
+ "meter_elasticsearch_index_search_query_avg_time",
+ "meter_elasticsearch_index_search_scroll_avg_time",
+ "meter_elasticsearch_index_search_suggest_avg_time"
+ ],
+ "expressionLabels": [
+ "fetch",
+ "query",
+ "scroll",
+ "suggest"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/envoy_ai_gateway.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/envoy_ai_gateway.json
new file mode 100644
index 0000000..4cf3a2b
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/envoy_ai_gateway.json
@@ -0,0 +1,406 @@
+{
+ "key": "ENVOY_AI_GATEWAY",
+ "alias": "Envoy AI Gateway",
+ "group": "Gateways",
+ "color": "var(--sw-pink)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-envoy-ai-gateway-monitoring/",
+ "aliases": {
+ "services": "AI Gateways",
+ "instances": "Nodes"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": false,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "cpm",
+ "columns": [
+ {
+ "metric": "cpm",
+ "label": "RPM",
+ "unit": "rpm",
+ "mqe": "meter_envoy_ai_gw_request_cpm",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "latency",
+ "label": "Avg Latency",
+ "unit": "ms",
+ "mqe": "meter_envoy_ai_gw_request_latency_avg",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "inTokens",
+ "label": "Input Tokens/min",
+ "mqe": "meter_envoy_ai_gw_input_token_rate",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "outTokens",
+ "label": "Output Tokens/min",
+ "mqe": "meter_envoy_ai_gw_output_token_rate",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Latency",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "latency",
+ "label": "Avg Latency",
+ "tip": "Average end-to-end request latency.",
+ "mqe": "meter_envoy_ai_gw_request_latency_avg",
+ "unit": "ms",
+ "aggregation": "avg"
+ },
+ {
+ "id": "ttft",
+ "label": "TTFT Avg",
+ "tip": "Time to first token \u2014 generative-model latency indicator.",
+ "mqe": "meter_envoy_ai_gw_ttft_avg",
+ "unit": "ms",
+ "aggregation": "avg"
+ }
+ ]
+ },
+ {
+ "title": "Tokens",
+ "size": "square",
+ "metrics": [
+ {
+ "id": "tokens",
+ "label": "Tokens/min",
+ "tip": "Combined input + output token rate.",
+ "mqe": "meter_envoy_ai_gw_input_token_rate + meter_envoy_ai_gw_output_token_rate",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "cpm",
+ "title": "Request RPM",
+ "type": "line",
+ "unit": "rpm",
+ "expressions": [
+ "meter_envoy_ai_gw_request_cpm"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency_avg",
+ "title": "Request Latency Avg",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_request_latency_avg"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "input_tokens",
+ "title": "Input Token Rate",
+ "type": "line",
+ "expressions": [
+ "meter_envoy_ai_gw_input_token_rate"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "output_tokens",
+ "title": "Output Token Rate",
+ "type": "line",
+ "expressions": [
+ "meter_envoy_ai_gw_output_token_rate"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency_percentile",
+ "title": "Request Latency Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_request_latency_percentile"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ },
+ {
+ "id": "ttft",
+ "title": "TTFT (Time to First Token)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_ttft_avg",
+ "meter_envoy_ai_gw_ttft_percentile"
+ ],
+ "expressionLabels": [
+ "avg",
+ "percentile"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "tpot",
+ "title": "TPOT (Time Per Output Token)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_tpot_avg",
+ "meter_envoy_ai_gw_tpot_percentile"
+ ],
+ "expressionLabels": [
+ "avg",
+ "percentile"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "provider_cpm",
+ "title": "RPM by Provider",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_provider_request_cpm,sum(gen_ai_provider_name))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "provider_tokens",
+ "title": "Tokens by Provider",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_provider_token_rate,sum(gen_ai_provider_name))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "provider_latency",
+ "title": "Latency Avg by Provider",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_provider_latency_avg,avg(gen_ai_provider_name))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "model_cpm",
+ "title": "RPM by Model",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_model_request_cpm,sum(gen_ai_response_model))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "model_tokens",
+ "title": "Tokens by Model",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_model_token_rate,sum(gen_ai_response_model))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "model_latency",
+ "title": "Latency Avg by Model",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_model_latency_avg,avg(gen_ai_response_model))"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "model_ttft",
+ "title": "TTFT by Model",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_model_ttft_avg,avg(gen_ai_response_model))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "model_tpot",
+ "title": "TPOT by Model",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_model_tpot_avg,avg(gen_ai_response_model))"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "mcp_cpm",
+ "title": "MCP RPM",
+ "tip": "Model Context Protocol \u2014 when used.",
+ "type": "line",
+ "expressions": [
+ "meter_envoy_ai_gw_mcp_request_cpm"
+ ],
+ "visibleWhen": "meter_envoy_ai_gw_mcp_request_cpm has value",
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "mcp_latency",
+ "title": "MCP Avg Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_mcp_request_latency_avg"
+ ],
+ "visibleWhen": "meter_envoy_ai_gw_mcp_request_latency_avg has value",
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "mcp_err",
+ "title": "MCP Error RPM",
+ "type": "line",
+ "expressions": [
+ "meter_envoy_ai_gw_mcp_error_cpm"
+ ],
+ "visibleWhen": "meter_envoy_ai_gw_mcp_error_cpm has value",
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "mcp_method",
+ "title": "MCP by Method",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_mcp_method_cpm,sum(mcp_method_name))"
+ ],
+ "visibleWhen": "meter_envoy_ai_gw_mcp_method_cpm has value",
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "mcp_backend",
+ "title": "MCP by Backend",
+ "type": "line",
+ "expressions": [
+ "aggregate_labels(meter_envoy_ai_gw_mcp_backend_request_cpm,sum(mcp_backend))"
+ ],
+ "visibleWhen": "meter_envoy_ai_gw_mcp_backend_request_cpm has value",
+ "span": 6,
+ "rowSpan": 2
+ }
+ ],
+ "instance": [
+ {
+ "id": "cpm",
+ "title": "Request RPM",
+ "type": "line",
+ "unit": "rpm",
+ "expressions": [
+ "meter_envoy_ai_gw_instance_request_cpm"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency_avg",
+ "title": "Request Latency Avg",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_instance_request_latency_avg"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "input_tokens",
+ "title": "Input Token Rate",
+ "type": "line",
+ "expressions": [
+ "meter_envoy_ai_gw_instance_input_token_rate"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "output_tokens",
+ "title": "Output Token Rate",
+ "type": "line",
+ "expressions": [
+ "meter_envoy_ai_gw_instance_output_token_rate"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "latency_percentile",
+ "title": "Request Latency Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_instance_request_latency_percentile"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ },
+ {
+ "id": "ttft",
+ "title": "TTFT",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_instance_ttft_avg",
+ "meter_envoy_ai_gw_instance_ttft_percentile"
+ ],
+ "expressionLabels": [
+ "avg",
+ "percentile"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "tpot",
+ "title": "TPOT",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_envoy_ai_gw_instance_tpot_avg",
+ "meter_envoy_ai_gw_instance_tpot_percentile"
+ ],
+ "expressionLabels": [
+ "avg",
+ "percentile"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/flink.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/flink.json
new file mode 100644
index 0000000..5afa613
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/flink.json
@@ -0,0 +1,561 @@
+{
+ "key": "FLINK",
+ "alias": "Flink",
+ "group": "MQ",
+ "color": "var(--sw-pink)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-flink-monitoring/",
+ "aliases": {
+ "services": "Flink JobManagers",
+ "instances": "TaskManagers",
+ "endpoints": "Jobs"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "jobs",
+ "columns": [
+ {
+ "metric": "jobs",
+ "label": "Running Jobs",
+ "mqe": "latest(meter_flink_jobManager_running_job_number)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "taskMgrs",
+ "label": "TaskManagers",
+ "mqe": "latest(meter_flink_jobManager_taskManagers_registered_number)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "slotsAvail",
+ "label": "Slots Available",
+ "mqe": "latest(meter_flink_jobManager_taskManagers_slots_available)",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "slotsTotal",
+ "label": "Slots Total",
+ "mqe": "latest(meter_flink_jobManager_taskManagers_slots_total)",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Cluster",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "jobs",
+ "label": "Running Jobs",
+ "tip": "Active Flink jobs.",
+ "mqe": "latest(meter_flink_jobManager_running_job_number)",
+ "aggregation": "sum"
+ },
+ {
+ "id": "slotsAvail",
+ "label": "Slots Available",
+ "tip": "Free task slots.",
+ "mqe": "latest(meter_flink_jobManager_taskManagers_slots_available)",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "jobs",
+ "title": "Running Jobs",
+ "type": "card",
+ "expressions": [
+ "latest(meter_flink_jobManager_running_job_number)"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "task_mgrs",
+ "title": "TaskManagers",
+ "type": "card",
+ "expressions": [
+ "latest(meter_flink_jobManager_taskManagers_registered_number)"
+ ],
+ "span": 3,
+ "rowSpan": 1
+ },
+ {
+ "id": "slots_total",
+ "title": "Slots Total",
+ "type": "card",
+ "expressions": [
+ "latest(meter_flink_jobManager_taskManagers_slots_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "slots_available",
+ "title": "Slots Available",
+ "type": "card",
+ "expressions": [
+ "latest(meter_flink_jobManager_taskManagers_slots_available)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "cpu_load",
+ "title": "JM JVM CPU Load (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "meter_flink_jobManager_jvm_cpu_load/10"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jm_threads",
+ "title": "JM JVM Thread Count",
+ "type": "line",
+ "expressions": [
+ "meter_flink_jobManager_jvm_thread_count"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jm_cpu_time",
+ "title": "JM CPU Time (ms)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_flink_jobManager_jvm_cpu_time/1000/1000"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jm_mem_heap",
+ "title": "JM Heap (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_flink_jobManager_jvm_memory_heap_used/1024/1024",
+ "(meter_flink_jobManager_jvm_memory_heap_available)/1024/1024"
+ ],
+ "expressionLabels": [
+ "used",
+ "available"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jm_mem_nonheap",
+ "title": "JM NonHeap (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "(meter_flink_jobManager_jvm_memory_nonHeap_used)/1024/1024",
+ "(meter_flink_jobManager_jvm_memory_nonHeap_available)/1024/1024"
+ ],
+ "expressionLabels": [
+ "used",
+ "available"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jm_metaspace",
+ "title": "JM Metaspace (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_flink_jobManager_jvm_memory_metaspace_used/1024/1024",
+ "(meter_flink_jobManager_jvm_memory_metaspace_available)/1024/1024"
+ ],
+ "expressionLabels": [
+ "used",
+ "available"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "g1_young",
+ "title": "G1 Young GC",
+ "type": "line",
+ "expressions": [
+ "meter_flink_jobManager_jvm_g1_young_generation_count",
+ "meter_flink_jobManager_jvm_g1_young_generation_time"
+ ],
+ "expressionLabels": [
+ "count",
+ "time (ms)"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "g1_old",
+ "title": "G1 Old GC",
+ "type": "line",
+ "expressions": [
+ "meter_flink_jobManager_jvm_g1_old_generation_count",
+ "meter_flink_jobManager_jvm_g1_old_generation_time"
+ ],
+ "expressionLabels": [
+ "count",
+ "time (ms)"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "all_gc",
+ "title": "All GC",
+ "type": "line",
+ "expressions": [
+ "meter_flink_jobManager_jvm_all_garbageCollector_count",
+ "meter_flink_jobManager_jvm_all_garbageCollector_time"
+ ],
+ "expressionLabels": [
+ "count",
+ "time (ms)"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "top_running",
+ "title": "Top 10 Running Jobs",
+ "type": "top",
+ "expressions": [
+ "top_n(meter_flink_job_runningTime,10,des)"
+ ],
+ "span": 12,
+ "rowSpan": 3
+ }
+ ],
+ "instance": [
+ {
+ "id": "cpu_load",
+ "title": "JVM CPU Load (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "meter_flink_taskManager_jvm_cpu_load/10"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "threads",
+ "title": "JVM Thread Count",
+ "type": "line",
+ "expressions": [
+ "meter_flink_taskManager_jvm_thread_count"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "cpu_time",
+ "title": "CPU Time (ms)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_flink_taskManager_jvm_cpu_time/1000/1000"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "back_pressure",
+ "title": "Back Pressured",
+ "type": "line",
+ "expressions": [
+ "meter_flink_taskManager_isBackPressured"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "mem_heap",
+ "title": "Heap (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_flink_taskManager_jvm_memory_heap_used/1024/1024",
+ "(meter_flink_taskManager_jvm_memory_heap_available)/1024/1024"
+ ],
+ "expressionLabels": [
+ "used",
+ "available"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "mem_nonheap",
+ "title": "NonHeap (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "(meter_flink_taskManager_jvm_memory_nonHeap_used)/1024/1024",
+ "(meter_flink_taskManager_jvm_memory_nonHeap_available)/1024/1024"
+ ],
+ "expressionLabels": [
+ "used",
+ "available"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "mem_meta",
+ "title": "Metaspace (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_flink_taskManager_jvm_memory_metaspace_used/1024/1024",
+ "(meter_flink_taskManager_jvm_memory_metaspace_available)/1024/1024"
+ ],
+ "expressionLabels": [
+ "used",
+ "available"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "records",
+ "title": "Records In / Out",
+ "type": "line",
+ "expressions": [
+ "meter_flink_taskManager_numRecordsIn",
+ "meter_flink_taskManager_numRecordsOut"
+ ],
+ "expressionLabels": [
+ "in",
+ "out"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "bytes",
+ "title": "Bytes In / Out / s",
+ "type": "line",
+ "expressions": [
+ "meter_flink_taskManager_numBytesInPerSecond",
+ "meter_flink_taskManager_numBytesOutPerSecond"
+ ],
+ "expressionLabels": [
+ "in",
+ "out"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "netty",
+ "title": "Netty Memory (MB)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_flink_taskManager_netty_usedMemory/1024/1024",
+ "meter_flink_taskManager_netty_availableMemory/1024/1024"
+ ],
+ "expressionLabels": [
+ "used",
+ "available"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "pool_usage",
+ "title": "Pool Usage (%)",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "meter_flink_taskManager_inPoolUsage*10",
+ "meter_flink_taskManager_outPoolUsage"
+ ],
+ "expressionLabels": [
+ "in pool",
+ "out pool"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "back_pressure_time",
+ "title": "Back-Pressure Time (ms/s)",
+ "type": "line",
+ "unit": "ms/s",
+ "expressions": [
+ "meter_flink_taskManager_softBackPressuredTimeMsPerSecond",
+ "meter_flink_taskManager_hardBackPressuredTimeMsPerSecond",
+ "meter_flink_taskManager_idleTimeMsPerSecond",
+ "meter_flink_taskManager_busyTimeMsPerSecond"
+ ],
+ "expressionLabels": [
+ "soft bp",
+ "hard bp",
+ "idle",
+ "busy"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "running_time",
+ "title": "Job Running Time (min)",
+ "type": "card",
+ "expressions": [
+ "latest(meter_flink_job_runningTime)/1000/60"
+ ],
+ "span": 4,
+ "rowSpan": 1
+ },
+ {
+ "id": "restarting_time",
+ "title": "Job Restarting Time (min)",
+ "type": "card",
+ "expressions": [
+ "latest(meter_flink_job_restartingTime)/1000/60"
+ ],
+ "span": 4,
+ "rowSpan": 1
+ },
+ {
+ "id": "cancelling_time",
+ "title": "Job Cancelling Time (min)",
+ "type": "card",
+ "expressions": [
+ "latest(meter_flink_job_cancellingTime)/1000/60"
+ ],
+ "span": 4,
+ "rowSpan": 1
+ },
+ {
+ "id": "restarts",
+ "title": "Job Restarts",
+ "type": "card",
+ "expressions": [
+ "latest(meter_flink_job_restart_number)"
+ ],
+ "span": 4,
+ "rowSpan": 1
+ },
+ {
+ "id": "checkpoints",
+ "title": "Checkpoints",
+ "type": "line",
+ "expressions": [
+ "meter_flink_job_checkpoints_total",
+ "meter_flink_job_checkpoints_completed",
+ "meter_flink_job_checkpoints_failed",
+ "meter_flink_job_checkpoints_inProgress"
+ ],
+ "expressionLabels": [
+ "total",
+ "completed",
+ "failed",
+ "in-progress"
+ ],
+ "span": 8,
+ "rowSpan": 2
+ },
+ {
+ "id": "checkpoint_last",
+ "title": "Last Checkpoint",
+ "type": "line",
+ "expressions": [
+ "meter_flink_job_lastCheckpointSize",
+ "meter_flink_job_lastCheckpointDuration"
+ ],
+ "expressionLabels": [
+ "size (bytes)",
+ "duration (ms)"
+ ],
+ "expressionAxes": [
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "emit_lag",
+ "title": "Current Emit Event Time Lag (ms)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_flink_job_currentEmitEventTimeLag"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "records",
+ "title": "Records In / Out",
+ "type": "line",
+ "expressions": [
+ "meter_flink_job_numRecordsIn",
+ "meter_flink_job_numRecordsOut"
+ ],
+ "expressionLabels": [
+ "in",
+ "out"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "bytes",
+ "title": "Bytes In / Out / s",
+ "type": "line",
+ "expressions": [
+ "meter_flink_job_numBytesInPerSecond",
+ "meter_flink_job_numBytesOutPerSecond"
+ ],
+ "expressionLabels": [
+ "in",
+ "out"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/general.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/general.json
new file mode 100644
index 0000000..c27261d
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/general.json
@@ -0,0 +1,980 @@
+{
+ "key": "GENERAL",
+ "alias": "General Service",
+ "color": "var(--sw-accent)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/service-agent/server-agents/",
+ "aliases": {
+ "services": "Services",
+ "instances": "Instances",
+ "endpoints": "API",
+ "endpointDependency": "API dependency"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "endpointDependency": true,
+ "topology": true,
+ "traces": true,
+ "logs": true,
+ "traceProfiling": true,
+ "ebpfProfiling": true,
+ "asyncProfiling": true,
+ "pprofProfiling": true
+ },
+ "layer-header": {
+ "orderBy": "cpm",
+ "columns": [
+ { "metric": "cpm", "label": "RPM", "mqe": "service_cpm", "aggregation": "sum" },
+ { "metric": "apdex", "label": "Apdex", "mqe": "service_apdex/10000", "aggregation": "avg" },
+ { "metric": "err", "label": "Error Rate", "unit": "%", "mqe": "100 - service_sla/100", "aggregation": "avg" }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Latency & errors",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "p95",
+ "label": "P95",
+ "tip": "95th-percentile response time across the layer.",
+ "mqe": "service_percentile{p='95'}",
+ "unit": "ms",
+ "aggregation": "avg"
+ },
+ {
+ "id": "err",
+ "label": "Error Rate",
+ "tip": "100 − SLA. Higher = worse.",
+ "mqe": "100 - service_sla/100",
+ "unit": "%",
+ "aggregation": "avg"
+ }
+ ]
+ },
+ {
+ "title": "Health",
+ "size": "square",
+ "metrics": [
+ {
+ "id": "apdex",
+ "label": "Apdex",
+ "tip": "User-satisfaction score on a 0 – 1 scale.",
+ "mqe": "service_apdex/10000",
+ "aggregation": "avg"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "top_apis",
+ "title": "Top 20 APIs",
+ "tip": "Top endpoints under the selected service. Switch tabs to re-rank by traffic, response time, or worst success rate.",
+ "type": "top",
+ "expressions": [
+ "top_n(endpoint_cpm,20,des)",
+ "top_n(endpoint_resp_time,20,des)",
+ "top_n(endpoint_sla,20,asc)/100"
+ ],
+ "expressionLabels": ["Traffic", "Slow", "Successful Rate"],
+ "expressionUnits": ["rpm", "ms", "%"],
+ "span": 3,
+ "rowSpan": 4
+ },
+ {
+ "id": "traffic_line",
+ "title": "Traffic",
+ "type": "line",
+ "unit": "rpm",
+ "expressions": ["service_cpm"],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "err_line",
+ "title": "Error Rate",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["100 - service_sla/100"],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "apdex_line",
+ "title": "Apdex",
+ "tip": "User-satisfaction score on a 0 – 1 scale.",
+ "type": "line",
+ "expressions": ["service_apdex/10000"],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "percentile_line",
+ "title": "Response Time Percentile",
+ "tip": "p50 / p75 / p90 / p95 / p99 latency — the tail of the response-time distribution.",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(service_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "resp_time_line",
+ "title": "Avg Response Time",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["service_resp_time"],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "mq_combined",
+ "title": "MQ Consume rate + latency",
+ "tip": "Count on the left axis, latency on the right.",
+ "type": "line",
+ "expressions": [
+ "service_mq_consume_count",
+ "service_mq_consume_latency"
+ ],
+ "expressionLabels": ["count", "latency"],
+ "expressionUnits": ["/min", "ms"],
+ "expressionAxes": [0, 1],
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "top_instance_load",
+ "title": "Top 10 instances by load",
+ "tip": "Ranked by traffic across the selected service's instances.",
+ "type": "top",
+ "unit": "rpm",
+ "expressions": ["top_n(service_instance_cpm,10,des)"],
+ "span": 4,
+ "rowSpan": 3
+ },
+ {
+ "id": "top_instance_slow",
+ "title": "Top 10 slowest instances",
+ "tip": "Ranked by average response time across the selected service's instances.",
+ "type": "top",
+ "unit": "ms",
+ "expressions": ["top_n(service_instance_resp_time,10,des)"],
+ "span": 4,
+ "rowSpan": 3
+ },
+ {
+ "id": "top_instance_sla",
+ "title": "Top 10 instances by success rate",
+ "tip": "top_n(service_instance_sla,10,asc)/100 — worst-first ranking.",
+ "type": "top",
+ "unit": "%",
+ "expressions": ["top_n(service_instance_sla,10,asc)/100"],
+ "span": 4,
+ "rowSpan": 3
+ },
+ {
+ "id": "slow_statements",
+ "title": "Slow Database Statements",
+ "tip": "Top 20 slowest database statements captured against this service. Sourced from top_n_service_database_statement. Shows `no data` when OAP has captured no statements in the window.",
+ "type": "record",
+ "unit": "ms",
+ "expressions": ["top_n(top_n_service_database_statement, 20, des)"],
+ "span": 12,
+ "rowSpan": 4
+ }
+ ],
+ "instance": [
+ {
+ "id": "instance_cpm_line",
+ "title": "Service Instance Load",
+ "tip": "Calls per minute against the selected instance.",
+ "type": "line",
+ "unit": "calls / min",
+ "expressions": ["service_instance_cpm"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "instance_resp_line",
+ "title": "Service Instance Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["service_instance_resp_time"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "instance_sla_line",
+ "title": "Service Instance Success Rate",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["service_instance_sla/100"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_cpu",
+ "title": "JVM CPU",
+ "tip": "JVM CPU as reported by the agent. Renders only when the instance is a JVM.",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["instance_jvm_cpu"],
+ "visibleWhen": "instance_jvm_cpu has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_memory",
+ "title": "JVM Memory",
+ "tip": "Heap / non-heap used + max in MB.",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "instance_jvm_memory_heap/1048576",
+ "instance_jvm_memory_heap_max/1048576",
+ "instance_jvm_memory_noheap/1048576",
+ "instance_jvm_memory_noheap_max/1048576"
+ ],
+ "expressionLabels": ["heap", "heap max", "non-heap", "non-heap max"],
+ "visibleWhen": "instance_jvm_memory_heap has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_memory_detail",
+ "title": "JVM Memory Detail",
+ "tip": "Per-pool used memory in MB. Covers code cache, young / old / survivor / permgen / metaspace, and the JIT codeheap sub-pools introduced in newer JVMs (Z heap, compressed class space, segmented codeheaps). Pools the JVM doesn't expose simply stay at 0.",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "instance_jvm_memory_pool_code_cache/1048576",
+ "instance_jvm_memory_pool_newgen/1048576",
+ "instance_jvm_memory_pool_oldgen/1048576",
+ "instance_jvm_memory_pool_survivor/1048576",
+ "instance_jvm_memory_pool_permgen/1048576",
+ "instance_jvm_memory_pool_metaspace/1048576",
+ "instance_jvm_memory_pool_zheap/1048576",
+ "instance_jvm_memory_pool_compressed_class_space/1048576",
+ "instance_jvm_memory_pool_codeheap_non_nmethods/1048576",
+ "instance_jvm_memory_pool_codeheap_profiled_nmethods/1048576",
+ "instance_jvm_memory_pool_codeheap_non_profiled_nmethods/1048576"
+ ],
+ "expressionLabels": [
+ "code cache",
+ "newgen",
+ "oldgen",
+ "survivor",
+ "permgen",
+ "metaspace",
+ "zheap",
+ "compressed class space",
+ "codeheap (non-nmethods)",
+ "codeheap (profiled nmethods)",
+ "codeheap (non-profiled nmethods)"
+ ],
+ "visibleWhen": "instance_jvm_memory_pool_code_cache has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_thread_count",
+ "title": "JVM Thread Count",
+ "type": "line",
+ "expressions": [
+ "instance_jvm_thread_live_count",
+ "instance_jvm_thread_daemon_count",
+ "instance_jvm_thread_peak_count"
+ ],
+ "expressionLabels": ["live", "daemon", "peak"],
+ "visibleWhen": "instance_jvm_thread_live_count has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_thread_state",
+ "title": "JVM Thread State Count",
+ "tip": "Threads by JVM state (runnable / blocked / waiting / timed-waiting).",
+ "type": "line",
+ "expressions": [
+ "instance_jvm_thread_runnable_state_thread_count",
+ "instance_jvm_thread_blocked_state_thread_count",
+ "instance_jvm_thread_waiting_state_thread_count",
+ "instance_jvm_thread_timed_waiting_state_thread_count"
+ ],
+ "expressionLabels": ["runnable", "blocked", "waiting", "timed-waiting"],
+ "visibleWhen": "instance_jvm_thread_runnable_state_thread_count has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_gc_time",
+ "title": "JVM GC Time",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "instance_jvm_young_gc_time",
+ "instance_jvm_old_gc_time",
+ "instance_jvm_normal_gc_time"
+ ],
+ "expressionLabels": ["young", "old", "normal"],
+ "visibleWhen": "instance_jvm_young_gc_time has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_gc_count",
+ "title": "JVM GC Count",
+ "type": "line",
+ "expressions": [
+ "instance_jvm_young_gc_count",
+ "instance_jvm_old_gc_count",
+ "instance_jvm_normal_gc_count"
+ ],
+ "expressionLabels": ["young", "old", "normal"],
+ "visibleWhen": "instance_jvm_young_gc_count has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "jvm_class_count",
+ "title": "JVM Class Count",
+ "type": "line",
+ "expressions": [
+ "instance_jvm_class_loaded_class_count",
+ "instance_jvm_class_total_loaded_class_count",
+ "instance_jvm_class_total_unloaded_class_count"
+ ],
+ "expressionLabels": ["loaded", "total loaded", "total unloaded"],
+ "visibleWhen": "instance_jvm_class_loaded_class_count has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "clr_cpu",
+ "title": "CLR CPU",
+ "tip": ".NET-only: renders when the instance reports CLR metrics.",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["instance_clr_cpu"],
+ "visibleWhen": "instance_clr_cpu has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "clr_thread",
+ "title": "CLR Thread",
+ "type": "line",
+ "expressions": [
+ "instance_clr_available_worker_threads",
+ "instance_clr_available_completion_port_threads",
+ "instance_clr_max_completion_port_threads"
+ ],
+ "expressionLabels": ["worker available", "completion-port available", "completion-port max"],
+ "visibleWhen": "instance_clr_available_worker_threads has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "clr_heap",
+ "title": "CLR Heap Memory",
+ "type": "line",
+ "unit": "MB",
+ "expressions": ["instance_clr_heap_memory/1048576"],
+ "visibleWhen": "instance_clr_heap_memory has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "clr_gc",
+ "title": "CLR GC",
+ "type": "line",
+ "expressions": [
+ "instance_clr_gen0_collect_count",
+ "instance_clr_gen1_collect_count",
+ "instance_clr_gen2_collect_count"
+ ],
+ "expressionLabels": ["gen 0", "gen 1", "gen 2"],
+ "visibleWhen": "instance_clr_gen0_collect_count has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_http_req_count",
+ "title": "Spring HTTP Request Count",
+ "tip": "Spring Sleuth / Spring Boot Actuator — http.server.requests counter.",
+ "type": "line",
+ "expressions": ["meter_http_server_requests_count"],
+ "visibleWhen": "meter_http_server_requests_count has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_http_req_duration",
+ "title": "Spring HTTP Request Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["meter_http_server_requests_duration"],
+ "visibleWhen": "meter_http_server_requests_duration has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_proc_cpu",
+ "title": "Spring Instance CPU Usage",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["meter_process_cpu_usage"],
+ "visibleWhen": "meter_process_cpu_usage has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_os_cpu",
+ "title": "Spring OS CPU Usage",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["meter_system_cpu_usage"],
+ "visibleWhen": "meter_system_cpu_usage has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_os_load",
+ "title": "Spring OS System Load",
+ "type": "line",
+ "expressions": ["meter_system_load_average_1m"],
+ "visibleWhen": "meter_system_load_average_1m has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_proc_files",
+ "title": "Spring OS Process Files",
+ "type": "line",
+ "expressions": [
+ "meter_process_files_open",
+ "meter_process_files_max"
+ ],
+ "expressionLabels": ["open", "max"],
+ "visibleWhen": "meter_process_files_open has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_jvm_gc_pause",
+ "title": "Spring JVM GC Pause Duration",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["meter_jvm_gc_pause_duration"],
+ "visibleWhen": "meter_jvm_gc_pause_duration has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_jvm_memory",
+ "title": "Spring JVM Memory",
+ "type": "line",
+ "unit": "MB",
+ "expressions": [
+ "meter_jvm_memory_used/1048576",
+ "meter_jvm_memory_max/1048576"
+ ],
+ "expressionLabels": ["used", "max"],
+ "visibleWhen": "meter_jvm_memory_used has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_jvm_thread",
+ "title": "Spring JVM Threads",
+ "type": "line",
+ "expressions": [
+ "meter_jvm_threads_live",
+ "meter_jvm_threads_daemon",
+ "meter_jvm_threads_peak"
+ ],
+ "expressionLabels": ["live", "daemon", "peak"],
+ "visibleWhen": "meter_jvm_threads_live has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_jvm_classes",
+ "title": "Spring JVM Classes",
+ "type": "line",
+ "expressions": [
+ "meter_jvm_classes_loaded",
+ "meter_jvm_classes_unloaded"
+ ],
+ "expressionLabels": ["loaded", "unloaded"],
+ "visibleWhen": "meter_jvm_classes_loaded has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_datasource",
+ "title": "Spring Database Connection Pool",
+ "tip": "HikariCP / Spring Boot datasource metrics — connection counts per pool.",
+ "type": "line",
+ "expressions": ["meter_datasource"],
+ "visibleWhen": "meter_datasource has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_thread_pool",
+ "title": "Spring Thread Pool",
+ "tip": "Spring Boot thread pool meters — active, queued, max threads per executor.",
+ "type": "line",
+ "expressions": ["meter_thread_pool"],
+ "visibleWhen": "meter_thread_pool has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_jdbc",
+ "title": "Spring JDBC Connections",
+ "type": "line",
+ "expressions": [
+ "meter_jdbc_connections_active",
+ "meter_jdbc_connections_idle",
+ "meter_jdbc_connections_max"
+ ],
+ "expressionLabels": ["active", "idle", "max"],
+ "visibleWhen": "meter_jdbc_connections_active has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "sleuth_tomcat",
+ "title": "Spring Tomcat Sessions",
+ "type": "line",
+ "expressions": [
+ "meter_tomcat_sessions_active_current",
+ "meter_tomcat_sessions_active_max",
+ "meter_tomcat_sessions_rejected"
+ ],
+ "expressionLabels": ["active", "max", "rejected"],
+ "visibleWhen": "meter_tomcat_sessions_active_current has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_goroutines",
+ "title": "Golang Goroutines / OS Threads",
+ "tip": "Golang agent: live goroutine count + underlying OS threads.",
+ "type": "line",
+ "expressions": [
+ "meter_instance_golang_live_goroutines_num",
+ "meter_instance_golang_os_threads_num"
+ ],
+ "expressionLabels": ["goroutines", "OS threads"],
+ "visibleWhen": "meter_instance_golang_live_goroutines_num has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_gc_pause",
+ "title": "Golang GC Pause Time (per minute)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["meter_instance_golang_gc_pauses/1000000"],
+ "visibleWhen": "meter_instance_golang_gc_pauses has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_gc_count",
+ "title": "Golang GC Count (per minute)",
+ "type": "line",
+ "expressions": ["meter_instance_golang_gc_count_labeled"],
+ "visibleWhen": "meter_instance_golang_gc_count_labeled has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_heap_alloc",
+ "title": "Golang Heap Alloc (per minute)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": ["meter_instance_golang_heap_alloc_rate/1048576"],
+ "visibleWhen": "meter_instance_golang_heap_alloc_rate has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_sched_lat",
+ "title": "Golang Goroutine Schedule Time",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["meter_instance_golang_sched_latencies/1000000"],
+ "visibleWhen": "meter_instance_golang_sched_latencies has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_heap_frees",
+ "title": "Golang GC Free (per minute)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": ["meter_instance_golang_heap_frees/1048576"],
+ "visibleWhen": "meter_instance_golang_heap_frees has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_alloc_size",
+ "title": "Golang Alloc Size (per minute)",
+ "type": "line",
+ "unit": "KB",
+ "expressions": ["meter_instance_golang_gc_heap_allocs_by_size/1024"],
+ "visibleWhen": "meter_instance_golang_gc_heap_allocs_by_size has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_free_size",
+ "title": "Golang Free Size (per minute)",
+ "type": "line",
+ "unit": "KB",
+ "expressions": ["meter_instance_golang_gc_heap_frees_by_size/1024"],
+ "visibleWhen": "meter_instance_golang_gc_heap_frees_by_size has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_heap_objects",
+ "title": "Golang Heap Objects",
+ "type": "line",
+ "expressions": ["meter_instance_golang_gc_heap_objects"],
+ "visibleWhen": "meter_instance_golang_gc_heap_objects has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_heap",
+ "title": "Golang Heap",
+ "type": "line",
+ "unit": "MB",
+ "expressions": ["meter_instance_golang_memory_heap_labeled/1048576"],
+ "visibleWhen": "meter_instance_golang_memory_heap_labeled has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_mspan",
+ "title": "Golang Metadata Mspan",
+ "type": "line",
+ "unit": "MB",
+ "expressions": ["meter_instance_golang_metadata_mspan_labeled"],
+ "visibleWhen": "meter_instance_golang_metadata_mspan_labeled has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_mcache",
+ "title": "Golang Metadata Mcache",
+ "type": "line",
+ "unit": "MB",
+ "expressions": ["meter_instance_golang_metadata_mcache_labeled"],
+ "visibleWhen": "meter_instance_golang_metadata_mcache_labeled has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_gc_goal",
+ "title": "Golang GC Goal Size",
+ "type": "line",
+ "unit": "MB",
+ "expressions": ["meter_instance_golang_gc_heap_goal/1048576"],
+ "visibleWhen": "meter_instance_golang_gc_heap_goal has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "go_cgo",
+ "title": "Golang CGO Calls (per minute)",
+ "type": "line",
+ "expressions": ["meter_instance_golang_cgo_calls"],
+ "visibleWhen": "meter_instance_golang_cgo_calls has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "pvm_cpu",
+ "title": "Python CPU Utilization",
+ "tip": "Python (PVM) agent: host vs process CPU.",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "meter_instance_pvm_total_cpu_utilization",
+ "meter_instance_pvm_process_cpu_utilization"
+ ],
+ "expressionLabels": ["host", "process"],
+ "visibleWhen": "meter_instance_pvm_process_cpu_utilization has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "pvm_mem",
+ "title": "Python Memory Utilization",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "meter_instance_pvm_total_mem_utilization",
+ "meter_instance_pvm_process_mem_utilization"
+ ],
+ "expressionLabels": ["host", "process"],
+ "visibleWhen": "meter_instance_pvm_process_mem_utilization has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "pvm_threads",
+ "title": "Python Thread Count",
+ "type": "line",
+ "expressions": ["meter_instance_pvm_thread_active_count"],
+ "visibleWhen": "meter_instance_pvm_thread_active_count has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "pvm_gc",
+ "title": "Python GC Count",
+ "type": "line",
+ "expressions": [
+ "meter_instance_pvm_gc_g0",
+ "meter_instance_pvm_gc_g1",
+ "meter_instance_pvm_gc_g2"
+ ],
+ "expressionLabels": ["gen 0", "gen 1", "gen 2"],
+ "visibleWhen": "meter_instance_pvm_gc_g0 has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "pvm_gc_time",
+ "title": "Python GC Time",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["meter_instance_pvm_gc_time"],
+ "visibleWhen": "meter_instance_pvm_gc_time has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "ruby_cpu",
+ "title": "Ruby CPU Usage",
+ "tip": "Ruby agent: process CPU as a percentage.",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["meter_instance_ruby_cpu_usage_percent"],
+ "visibleWhen": "meter_instance_ruby_cpu_usage_percent has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "ruby_mem_rss",
+ "title": "Ruby Memory (RSS)",
+ "type": "line",
+ "unit": "MB",
+ "expressions": ["meter_instance_ruby_memory_rss_mb"],
+ "visibleWhen": "meter_instance_ruby_memory_rss_mb has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "ruby_mem_usage",
+ "title": "Ruby Memory Usage",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["meter_instance_ruby_memory_usage_percent"],
+ "visibleWhen": "meter_instance_ruby_memory_usage_percent has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "ruby_threads",
+ "title": "Ruby Thread Status",
+ "type": "line",
+ "expressions": [
+ "meter_instance_ruby_thread_count_active",
+ "meter_instance_ruby_thread_count_running"
+ ],
+ "expressionLabels": ["active", "running"],
+ "visibleWhen": "meter_instance_ruby_thread_count_active has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "ruby_gc",
+ "title": "Ruby GC Count",
+ "type": "line",
+ "expressions": [
+ "meter_instance_ruby_gc_count_total",
+ "meter_instance_ruby_gc_minor_count_total",
+ "meter_instance_ruby_gc_major_count_total"
+ ],
+ "expressionLabels": ["total", "minor", "major"],
+ "visibleWhen": "meter_instance_ruby_gc_count_total has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "ruby_gc_time",
+ "title": "Ruby GC Time",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["meter_instance_ruby_gc_time_total"],
+ "visibleWhen": "meter_instance_ruby_gc_time_total has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "ruby_heap_usage",
+ "title": "Ruby Heap Usage",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["meter_instance_ruby_heap_usage_percent"],
+ "visibleWhen": "meter_instance_ruby_heap_usage_percent has value",
+ "span": 3,
+ "rowSpan": 2
+ },
+ {
+ "id": "ruby_heap_slots",
+ "title": "Ruby Heap Slots",
+ "type": "line",
+ "expressions": [
+ "meter_instance_ruby_heap_live_slots_count",
+ "meter_instance_ruby_heap_available_slots_count"
+ ],
+ "expressionLabels": ["live", "available"],
+ "visibleWhen": "meter_instance_ruby_heap_live_slots_count has value",
+ "span": 3,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "endpoint_cpm_line",
+ "title": "Traffic",
+ "type": "line",
+ "unit": "rpm",
+ "expressions": ["endpoint_cpm"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "endpoint_resp_line",
+ "title": "Response Time",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["endpoint_resp_time"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "endpoint_sla_line",
+ "title": "Success Rate",
+ "type": "line",
+ "unit": "%",
+ "expressions": ["endpoint_sla/100"],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "endpoint_percentile",
+ "title": "Response Time Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(endpoint_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ },
+ {
+ "id": "endpoint_mq_consume_latency",
+ "title": "MQ Avg Consuming Latency",
+ "tip": "Endpoint-scope consume latency — only populated for endpoints serving message-queue traffic.",
+ "type": "line",
+ "unit": "ms",
+ "expressions": ["endpoint_mq_consume_latency"],
+ "visibleWhen": "endpoint_mq_consume_latency has value",
+ "span": 12,
+ "rowSpan": 2
+ }
+ ],
+ "trace": [],
+ "traceProfiling": [],
+ "ebpfProfiling": [],
+ "asyncProfiling": []
+ },
+ "topology": {
+ "nodeMetrics": [
+ { "id": "cpm", "label": "RPM", "mqe": "service_cpm", "unit": "rpm", "role": "center", "aggregation": "avg" },
+ {
+ "id": "sla",
+ "label": "SLA",
+ "mqe": "service_sla/100",
+ "unit": "%",
+ "role": "ring",
+ "aggregation": "avg",
+ "thresholds": {
+ "invertHealth": true,
+ "invertBase": 100,
+ "ok": 0.1,
+ "warn": 1,
+ "danger": 5
+ }
+ },
+ { "id": "respTime", "label": "Latency", "mqe": "service_resp_time", "unit": "ms", "role": "secondary", "aggregation": "avg" }
+ ],
+ "linkServerMetrics": [
+ { "id": "cpm", "label": "RPM", "mqe": "service_relation_server_cpm", "unit": "rpm", "role": "lineServer", "aggregation": "avg" },
+ { "id": "respTime", "label": "Avg response time", "mqe": "service_relation_server_resp_time", "unit": "ms", "aggregation": "avg" },
+ { "id": "p95", "label": "p95", "mqe": "service_relation_server_percentile{p='95'}", "unit": "ms", "aggregation": "avg" },
+ { "id": "sla", "label": "SLA", "mqe": "service_relation_server_call_sla/100", "unit": "%", "aggregation": "avg" }
+ ],
+ "linkClientMetrics": [
+ { "id": "cpm", "label": "RPM", "mqe": "service_relation_client_cpm", "unit": "rpm", "role": "lineClient", "aggregation": "avg" },
+ { "id": "respTime", "label": "Avg response time", "mqe": "service_relation_client_resp_time", "unit": "ms", "aggregation": "avg" },
+ { "id": "p95", "label": "p95", "mqe": "service_relation_client_percentile{p='95'}", "unit": "ms", "aggregation": "avg" },
+ { "id": "sla", "label": "SLA", "mqe": "service_relation_client_call_sla/100", "unit": "%", "aggregation": "avg" }
+ ]
+ },
+ "endpointDependency": {
+ "nodeMetrics": [
+ { "id": "cpm", "label": "RPM", "mqe": "endpoint_cpm", "unit": "rpm", "role": "center", "aggregation": "avg" },
+ {
+ "id": "sla",
+ "label": "SLA",
+ "mqe": "endpoint_sla/100",
+ "unit": "%",
+ "role": "ring",
+ "aggregation": "avg",
+ "thresholds": {
+ "invertHealth": true,
+ "invertBase": 100,
+ "ok": 0.1,
+ "warn": 1,
+ "danger": 5
+ }
+ },
+ { "id": "respTime", "label": "Latency", "mqe": "endpoint_resp_time", "unit": "ms", "role": "secondary", "aggregation": "avg" }
+ ],
+ "linkMetrics": [
+ { "id": "cpm", "label": "RPM", "mqe": "endpoint_relation_cpm", "unit": "rpm", "role": "lineServer", "aggregation": "avg" },
+ { "id": "respTime", "label": "Avg response time", "mqe": "endpoint_relation_resp_time", "unit": "ms", "aggregation": "avg" },
+ { "id": "p95", "label": "p95", "mqe": "endpoint_relation_percentile{p='95'}", "unit": "ms", "aggregation": "avg" },
+ { "id": "sla", "label": "SLA", "mqe": "endpoint_relation_sla/100", "unit": "%", "aggregation": "avg" }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/ios.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/ios.json
new file mode 100644
index 0000000..c18af1b
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/ios.json
@@ -0,0 +1,343 @@
+{
+ "key": "IOS",
+ "alias": "iOS",
+ "group": "Mobile",
+ "color": "var(--sw-cyan)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-ios-monitoring/",
+ "aliases": {
+ "services": "Apps",
+ "instances": "App Sessions",
+ "endpoints": "Outbound APIs"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": true,
+ "topology": false,
+ "traces": false,
+ "logs": true
+ },
+ "layer-header": {
+ "orderBy": "launch",
+ "columns": [
+ {
+ "metric": "launch",
+ "label": "Launch (P95)",
+ "unit": "ms",
+ "mqe": "meter_ios_app_launch_time_percentile{p='95'}",
+ "aggregation": "avg"
+ },
+ {
+ "metric": "hangSum",
+ "label": "Hang Time",
+ "unit": "ms",
+ "mqe": "meter_ios_hang_time_sum",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "abnormalExit",
+ "label": "Crashes",
+ "mqe": "meter_ios_foreground_abnormal_exit_count + meter_ios_background_abnormal_exit_count",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "outboundCpm",
+ "label": "Outbound RPM",
+ "unit": "rpm",
+ "mqe": "service_cpm",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "App health",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "launch_p95",
+ "label": "Launch P95",
+ "tip": "95th-percentile cold-launch time as measured by MetricKit.",
+ "mqe": "meter_ios_app_launch_time_percentile{p='95'}",
+ "unit": "ms",
+ "aggregation": "avg"
+ },
+ {
+ "id": "hang_p95",
+ "label": "Hang P95",
+ "tip": "95th-percentile UI hang duration.",
+ "mqe": "meter_ios_hang_time_percentile{p='95'}",
+ "unit": "ms",
+ "aggregation": "avg"
+ },
+ {
+ "id": "crashes",
+ "label": "Crashes",
+ "tip": "Foreground + background abnormal exits.",
+ "mqe": "meter_ios_foreground_abnormal_exit_count + meter_ios_background_abnormal_exit_count",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "launch_percentile",
+ "title": "App Launch Time Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(meter_ios_app_launch_time_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "hang_percentile",
+ "title": "Hang Time Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(meter_ios_hang_time_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "hang_sum",
+ "title": "Hang Time (sum)",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "meter_ios_hang_time_sum"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "abnormal_exits",
+ "title": "Abnormal Exits (Crashes)",
+ "tip": "MetricKit reports foreground vs background abnormal exits separately.",
+ "type": "line",
+ "expressions": [
+ "meter_ios_foreground_abnormal_exit_count",
+ "meter_ios_background_abnormal_exit_count"
+ ],
+ "expressionLabels": [
+ "foreground",
+ "background"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "oom_kills",
+ "title": "OOM Kill Count",
+ "tip": "Background OOM kills \u2014 the iOS system reaping the app due to memory pressure.",
+ "type": "line",
+ "expressions": [
+ "meter_ios_background_oom_kill_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "peak_memory",
+ "title": "Peak Memory",
+ "type": "line",
+ "unit": "bytes",
+ "expressions": [
+ "meter_ios_peak_memory"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "scroll_hitch",
+ "title": "Scroll Hitch Ratio",
+ "tip": "Percentage of scroll frames classified as hitched \u2014 higher = laggier UI.",
+ "type": "line",
+ "expressions": [
+ "meter_ios_scroll_hitch_ratio"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "network_transfer",
+ "title": "Network Transfer",
+ "tip": "Bytes over wifi vs cellular, in + out.",
+ "type": "line",
+ "unit": "bytes",
+ "expressions": [
+ "meter_ios_wifi_download",
+ "meter_ios_wifi_upload",
+ "meter_ios_cellular_download",
+ "meter_ios_cellular_upload"
+ ],
+ "expressionLabels": [
+ "wifi \u2193",
+ "wifi \u2191",
+ "cellular \u2193",
+ "cellular \u2191"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "outbound_http",
+ "title": "Outbound HTTP",
+ "tip": "Calls the app makes to backends \u2014 load, latency, success rate.",
+ "type": "line",
+ "expressions": [
+ "service_cpm",
+ "service_resp_time",
+ "service_sla/100"
+ ],
+ "expressionLabels": [
+ "RPM",
+ "Avg latency (ms)",
+ "Success Rate (%)"
+ ],
+ "expressionAxes": [
+ 0,
+ 0,
+ 1
+ ],
+ "span": 6,
+ "rowSpan": 2
+ }
+ ],
+ "instance": [
+ {
+ "id": "instance_launch_p",
+ "title": "Launch Time Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(meter_ios_instance_app_launch_time_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "instance_hang_p",
+ "title": "Hang Time Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(meter_ios_instance_hang_time_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 6,
+ "rowSpan": 2
+ },
+ {
+ "id": "instance_crashes",
+ "title": "Abnormal Exits",
+ "type": "line",
+ "expressions": [
+ "meter_ios_instance_foreground_abnormal_exit_count",
+ "meter_ios_instance_background_abnormal_exit_count"
+ ],
+ "expressionLabels": [
+ "foreground",
+ "background"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "instance_oom",
+ "title": "OOM Kill Count",
+ "type": "line",
+ "expressions": [
+ "meter_ios_instance_background_oom_kill_count"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "instance_peak_mem",
+ "title": "Peak Memory",
+ "type": "line",
+ "unit": "bytes",
+ "expressions": [
+ "meter_ios_instance_peak_memory"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "instance_outbound",
+ "title": "Outbound HTTP",
+ "type": "line",
+ "expressions": [
+ "service_instance_cpm",
+ "service_instance_resp_time",
+ "service_instance_sla/100"
+ ],
+ "expressionLabels": [
+ "RPM",
+ "Avg latency (ms)",
+ "Success Rate (%)"
+ ],
+ "expressionAxes": [
+ 0,
+ 0,
+ 1
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ],
+ "endpoint": [
+ {
+ "id": "ep_cpm",
+ "title": "Outbound Load",
+ "type": "line",
+ "unit": "rpm",
+ "expressions": [
+ "endpoint_cpm"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ep_resp",
+ "title": "Outbound Avg Latency",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "endpoint_resp_time"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ep_sla",
+ "title": "Outbound Success Rate",
+ "type": "line",
+ "unit": "%",
+ "expressions": [
+ "endpoint_sla/100"
+ ],
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "ep_percentile",
+ "title": "Outbound Latency Percentile",
+ "type": "line",
+ "unit": "ms",
+ "expressions": [
+ "relabels(endpoint_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='50,75,90,95,99')"
+ ],
+ "span": 12,
+ "rowSpan": 2
+ }
+ ]
+ }
+}
diff --git a/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/k8s.json b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/k8s.json
new file mode 100644
index 0000000..c0d3fdd
--- /dev/null
+++ b/scripts/.finalize-work/src/apps/bff/src/bundled_templates/layers/k8s.json
@@ -0,0 +1,461 @@
+{
+ "key": "K8S",
+ "alias": "Kubernetes",
+ "group": "Kubernetes",
+ "color": "var(--sw-info)",
+ "documentLink": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-k8s-monitoring/",
+ "aliases": {
+ "services": "Clusters",
+ "instances": "Nodes"
+ },
+ "components": {
+ "service": true,
+ "instances": true,
+ "endpoints": false,
+ "endpointDependency": false,
+ "topology": false,
+ "traces": false,
+ "logs": false
+ },
+ "layer-header": {
+ "orderBy": "pods",
+ "columns": [
+ {
+ "metric": "pods",
+ "label": "Pods",
+ "mqe": "k8s_cluster_pod_total",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "nodes",
+ "label": "Nodes",
+ "mqe": "k8s_cluster_node_total",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "namespaces",
+ "label": "Namespaces",
+ "mqe": "k8s_cluster_namespace_total",
+ "aggregation": "sum"
+ },
+ {
+ "metric": "deployments",
+ "label": "Deployments",
+ "mqe": "k8s_cluster_deployment_total",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ "overview": {
+ "groups": [
+ {
+ "title": "Capacity",
+ "size": "auto",
+ "metrics": [
+ {
+ "id": "pods",
+ "label": "Pods",
+ "tip": "Total pods reported across the cluster.",
+ "mqe": "k8s_cluster_pod_total",
+ "aggregation": "sum"
+ },
+ {
+ "id": "nodes",
+ "label": "Nodes",
+ "tip": "Node count.",
+ "mqe": "k8s_cluster_node_total",
+ "aggregation": "sum"
+ },
+ {
+ "id": "services",
+ "label": "Services",
+ "tip": "K8s Service objects.",
+ "mqe": "k8s_cluster_service_total",
+ "aggregation": "sum"
+ }
+ ]
+ },
+ {
+ "title": "Trouble",
+ "size": "square",
+ "metrics": [
+ {
+ "id": "podsNotRunning",
+ "label": "Pods not running",
+ "tip": "Pods in any non-Running state \u2014 pending, failed, unknown.",
+ "mqe": "latest(k8s_cluster_pod_status_not_running)",
+ "aggregation": "sum"
+ }
+ ]
+ }
+ ]
+ },
+ "dashboards": {
+ "service": [
+ {
+ "id": "node_total",
+ "title": "Node Total",
+ "type": "card",
+ "expressions": [
+ "latest(k8s_cluster_node_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "namespace_total",
+ "title": "Namespace Total",
+ "type": "card",
+ "expressions": [
+ "latest(k8s_cluster_namespace_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "deployment_total",
+ "title": "Deployment Total",
+ "type": "card",
+ "expressions": [
+ "latest(k8s_cluster_deployment_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "statefulset_total",
+ "title": "StatefulSet Total",
+ "type": "card",
+ "expressions": [
+ "latest(k8s_cluster_statefulset_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "daemonset_total",
+ "title": "DaemonSet Total",
+ "type": "card",
+ "expressions": [
+ "latest(k8s_cluster_daemonset_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "service_total",
+ "title": "Service Total",
+ "type": "card",
+ "expressions": [
+ "latest(k8s_cluster_service_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "pod_total",
+ "title": "Pod Total",
+ "type": "card",
+ "expressions": [
+ "latest(k8s_cluster_pod_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "container_total",
+ "title": "Container Total",
+ "type": "card",
+ "expressions": [
+ "latest(k8s_cluster_container_total)"
+ ],
+ "span": 3,
+ "rowSpan": 1,
+ "format": "int"
+ },
+ {
+ "id": "cpu_res",
+ "title": "CPU Resources",
+ "tip": "Cluster CPU capacity vs requests / limits / allocatable (millicores).",
+ "type": "line",
+ "expressions": [
+ "k8s_cluster_cpu_cores",
+ "k8s_cluster_cpu_cores_requests",
+ "k8s_cluster_cpu_cores_limits",
+ "k8s_cluster_cpu_cores_allocatable"
+ ],
+ "expressionLabels": [
+ "Capacity",
+ "Requests",
+ "Limits",
+ "Allocatable"
+ ],
+ "unit": "m",
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "mem_res",
+ "title": "Memory Resources",
+ "tip": "Cluster memory requests / allocatable / limits / total.",
+ "type": "line",
+ "expressions": [
+ "k8s_cluster_memory_requests/1024/1024/1024",
+ "k8s_cluster_memory_allocatable/1024/1024/1024",
+ "k8s_cluster_memory_limits/1024/1024/1024",
+ "k8s_cluster_memory_total/1024/1024/1024"
+ ],
+ "expressionLabels": [
+ "Requests",
+ "Allocatable",
+ "Limits",
+ "Total"
+ ],
+ "unit": "Gi",
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "storage_res",
+ "title": "Storage Resources",
+ "tip": "Cluster ephemeral-storage total vs allocatable.",
+ "type": "line",
+ "expressions": [
+ "k8s_cluster_storage_total/1024/1024/1024",
+ "k8s_cluster_storage_allocatable/1024/1024/1024"
+ ],
+ "expressionLabels": [
+ "Total",
+ "Allocatable"
+ ],
+ "unit": "Gi",
+ "span": 4,
+ "rowSpan": 2
+ },
+ {
+ "id": "node_status",
+ "title": "Node Status",
+ "tip": "Per-node Kubernetes conditions currently true/unknown (Ready, *Pressure, \u2026).",
+ "type": "table",
+ "expressions": [
+ "latest(k8s_cluster_node_status)"
+ ],
+ "tableHeaders": [
+ "Node \u00b7 Condition",
+ ""
+ ],
+ "showTableValues": false,
+ "span": 4,
+ "rowSpan": 4
+ },
+ {
+ "id": "deployment_status",
+ "title": "Deployment Status",
+ "tip": "Deployments reporting the Available condition.",
+ "type": "table",
+ "expressions": [
+ "latest(k8s_cluster_deployment_status)"
+ ],
+ "tableHeaders": [
+ "Deployment \u00b7 Available",
+ ""
+ ],
+ "showTableValues": false,
+ "span": 4,
+ "rowSpan": 4
+ },
+ {
+ "id": "deployment_replicas",
+ "title": "Deployment Spec Replicas",
+ "tip": "Desired replica count per deployment.",
+ "type": "table",
+ "expressions": [
... 109269 lines suppressed ...