This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
The following commit(s) were added to refs/heads/main by this push:
new a4e00cc bff/ui: clear non-JSON OAP error + hide redundant cluster
link on cluster page
a4e00cc is described below
commit a4e00ccaeae11cf39a49f3dbc254ef18ef4b7374
Author: Wu Sheng <[email protected]>
AuthorDate: Wed May 20 16:51:46 2026 +0800
bff/ui: clear non-JSON OAP error + hide redundant cluster link on cluster
page
graphqlPost: a 200 response that isn't JSON (OAP's GraphQL port actually
serving HTML — a proxy/SPA fallback, login redirect, or the OAP UI
itself) was parsed with res.json(), yielding the opaque
`Unexpected token '<', "<!doctype "... is not valid JSON`. Detect
non-JSON by content-type / leading char and throw a diagnostic with the
HTTP status, content-type, and a body snippet, plus the hint to check
oap.queryUrl points at the GraphQL port. (It is NOT an RBAC block — the
upstream is returning HTML.)
GlobalConnectivityBanner: hide the "View cluster status →" shortcut when
the operator is already on /operate/cluster — it pointed at the page
they're already viewing.
---
apps/bff/src/client/graphql.ts | 23 ++++++++++++++++++++++-
apps/ui/src/shell/GlobalConnectivityBanner.vue | 5 ++++-
2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/apps/bff/src/client/graphql.ts b/apps/bff/src/client/graphql.ts
index ceb5558..d48d959 100644
--- a/apps/bff/src/client/graphql.ts
+++ b/apps/bff/src/client/graphql.ts
@@ -102,7 +102,28 @@ export async function graphqlPost<T>(
const text = await res.text().catch(() => '');
throw new GraphqlError(res.status, `graphql http ${res.status}:
${text.slice(0, 200)}`);
}
- const body = (await res.json()) as { data?: T; errors?: ReadonlyArray<{
message: string; path?: ReadonlyArray<string | number> }> };
+ // A 200 that isn't JSON means the endpoint isn't actually OAP's
+ // GraphQL — most commonly it's serving an HTML page (a reverse proxy
+ // / SPA fallback, a login redirect, or the OAP UI itself). Parsing
+ // that as JSON yields the opaque `Unexpected token '<'`; surface a
+ // clear diagnostic with the status, content-type, and a snippet.
+ const raw = await res.text();
+ const ctype = res.headers.get('content-type') ?? '';
+ const looksJson = ctype.includes('json') || /^\s*[{[]/.test(raw);
+ if (!looksJson) {
+ const snippet = raw.replace(/\s+/g, ' ').trim().slice(0, 120);
+ throw new GraphqlError(
+ res.status,
+ `OAP query endpoint returned non-JSON (HTTP ${res.status}, content-type
"${ctype || 'unknown'}") — ` +
+ `expected GraphQL JSON. Check oap.queryUrl points at OAP's GraphQL
port, not a UI/proxy. Body: ${snippet}`,
+ );
+ }
+ let body: { data?: T; errors?: ReadonlyArray<{ message: string; path?:
ReadonlyArray<string | number> }> };
+ try {
+ body = JSON.parse(raw);
+ } catch (e) {
+ throw new GraphqlError(res.status, `OAP returned malformed JSON: ${e
instanceof Error ? e.message : String(e)}`);
+ }
if (body.errors && body.errors.length) {
throw new GraphqlError(200, body.errors.map((e) => e.message).join('; '),
body.errors);
}
diff --git a/apps/ui/src/shell/GlobalConnectivityBanner.vue
b/apps/ui/src/shell/GlobalConnectivityBanner.vue
index bdc3615..204d27c 100644
--- a/apps/ui/src/shell/GlobalConnectivityBanner.vue
+++ b/apps/ui/src/shell/GlobalConnectivityBanner.vue
@@ -131,6 +131,9 @@ watch([unreachable, retryChoice], () => startRetry(), {
immediate: true });
onBeforeUnmount(() => clearRetry());
const errorText = computed<string>(() => info.value?.error ?? 'no response');
+// The "View cluster status" shortcut is pointless once the operator is
+// already on the cluster page — hide it there.
+const onClusterPage = computed<boolean>(() =>
route.path.startsWith('/operate/cluster'));
const queryUrl = computed<string | undefined>(() => info.value?.queryUrl);
</script>
@@ -160,7 +163,7 @@ const queryUrl = computed<string | undefined>(() =>
info.value?.queryUrl);
</label>
<button type="button" class="now" @click="() => refetch()">retry
now</button>
- <RouterLink to="/operate/cluster" class="link">View cluster status
→</RouterLink>
+ <RouterLink v-if="!onClusterPage" to="/operate/cluster" class="link">View
cluster status →</RouterLink>
</div>
</template>