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>
 

Reply via email to