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 313072d  fix: network task list query + refresh button on all 
profiling views
313072d is described below

commit 313072d69e5d6c5da3f8dbc5606174807e6a8e0c
Author: Wu Sheng <[email protected]>
AuthorDate: Wed May 20 09:16:35 2026 +0800

    fix: network task list query + refresh button on all profiling views
    
    Network task list was empty even after a successful create:
      The list query filtered on triggerType CONTINUOUS_PROFILING, but OAP
      stores network-profiling tasks with FIXED_TIME (verified in
      EBPFProfilingMutationService line 151:
      task.setTriggerType(EBPFProfilingTriggerType.FIXED_TIME) for
      createEBPFNetworkProfiling). The topology query is independent of the
      task list, which is why the graph rendered while the list stayed
      empty. Switch the filter to FIXED_TIME — matches booster-ui's
      network Tasks.vue.
    
    Refresh button on every profiling task list:
      eBPF already had one; add the same ↻ button (Icon name="refresh",
      spins while tasksLoading, disabled until the upstream selection
      exists) to the network / trace / async / pprof task-list heads.
      Each calls its existing refreshTasks(), so the operator can pick up
      a newly-created task — or one that transitioned state on OAP —
      without a full-page reload that resets the rest of the view.
---
 apps/bff/src/http/query/ebpf.ts                    |  8 +++-
 .../layer/profiling/LayerAsyncProfilingView.vue    | 43 ++++++++++++++++---
 .../layer/profiling/LayerNetworkProfilingView.vue  | 43 ++++++++++++++++---
 .../layer/profiling/LayerPprofProfilingView.vue    | 35 +++++++++++++++-
 .../layer/profiling/LayerTraceProfilingView.vue    | 48 ++++++++++++++++++----
 5 files changed, 157 insertions(+), 20 deletions(-)

diff --git a/apps/bff/src/http/query/ebpf.ts b/apps/bff/src/http/query/ebpf.ts
index cbb613b..712be4e 100644
--- a/apps/bff/src/http/query/ebpf.ts
+++ b/apps/bff/src/http/query/ebpf.ts
@@ -322,7 +322,11 @@ export function registerEBPFRoutes(app: FastifyInstance, 
deps: EBPFRouteDeps): v
 
   // ── network profiling ─────────────────────────────────────────────
   /** List network-profile tasks for a service. Same OAP entry-point as
-   *  ON_CPU/OFF_CPU tasks, just with target=NETWORK + 
trigger=CONTINUOUS_PROFILING. */
+   *  ON_CPU/OFF_CPU tasks, with target=NETWORK. OAP stores network tasks
+   *  with trigger FIXED_TIME (EBPFProfilingMutationService sets
+   *  TriggerType.FIXED_TIME for createEBPFNetworkProfiling), so the
+   *  list query must filter on FIXED_TIME — CONTINUOUS_PROFILING returns
+   *  nothing and the just-created task never shows up. */
   app.get(
     '/api/layer/:key/ebpf/network/tasks',
     { preHandler: auth },
@@ -350,7 +354,7 @@ export function registerEBPFRoutes(app: FastifyInstance, 
deps: EBPFRouteDeps): v
             serviceId: serviceId ?? undefined,
             serviceInstanceId: instanceArg || undefined,
             targets: ['NETWORK'],
-            triggerType: 'CONTINUOUS_PROFILING',
+            triggerType: 'FIXED_TIME',
           },
         );
         payload.tasks = data.queryEBPFTasks ?? [];
diff --git a/apps/ui/src/layer/profiling/LayerAsyncProfilingView.vue 
b/apps/ui/src/layer/profiling/LayerAsyncProfilingView.vue
index a3b6193..3901422 100644
--- a/apps/ui/src/layer/profiling/LayerAsyncProfilingView.vue
+++ b/apps/ui/src/layer/profiling/LayerAsyncProfilingView.vue
@@ -36,6 +36,7 @@ import type {
   ProfileAnalyzationTree,
 } from '@/api/client';
 import ProfileFlameGraph from '@/layer/profiling/ProfileFlameGraph.vue';
+import Icon from '@/components/icons/Icon.vue';
 
 const route = useRoute();
 const layerKey = computed(() => String(route.params.layerKey ?? ''));
@@ -204,11 +205,21 @@ function instanceName(id: string): string {
     <div class="ap-side">
       <div class="side-head between">
         <span>Async profile tasks</span>
-        <button
-          class="btn-new"
-          :disabled="!serviceId"
-          @click="showNewTask = true"
-        >+ New Task</button>
+        <div class="side-head-actions">
+          <button
+            class="btn-refresh"
+            :class="{ spinning: tasksLoading }"
+            :disabled="!serviceId || tasksLoading"
+            :title="!serviceId ? 'Pick a service first' : tasksLoading ? 
'Refreshing…' : 'Refresh task list'"
+            aria-label="Refresh task list"
+            @click="refreshTasks"
+          ><Icon name="refresh" :size="11" /></button>
+          <button
+            class="btn-new"
+            :disabled="!serviceId"
+            @click="showNewTask = true"
+          >+ New Task</button>
+        </div>
       </div>
       <div v-if="tasksError" class="side-err">{{ tasksError }}</div>
       <div v-else-if="tasksLoading && !tasks.length" 
class="side-empty">Loading…</div>
@@ -374,6 +385,28 @@ function instanceName(id: string): string {
   color: var(--sw-fg-1);
   cursor: pointer;
 }
+.side-head-actions { display: inline-flex; align-items: center; gap: 6px; }
+.btn-refresh {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  padding: 2px 6px;
+  border-radius: 3px;
+  border: 1px solid var(--sw-line-2);
+  background: var(--sw-bg-1);
+  color: var(--sw-fg-1);
+  cursor: pointer;
+  line-height: 0;
+}
+.btn-refresh:hover:not(:disabled) { border-color: var(--sw-accent); color: 
var(--sw-accent); }
+.btn-refresh:disabled { opacity: 0.5; cursor: not-allowed; }
+.btn-refresh.spinning :deep(svg) {
+  animation: prof-refresh-spin 1.6s linear infinite;
+  transform-origin: 50% 50%;
+}
+@keyframes prof-refresh-spin {
+  to { transform: rotate(360deg); }
+}
 .btn-new:hover:not(:disabled) {
   border-color: var(--sw-accent);
   color: var(--sw-accent);
diff --git a/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue 
b/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
index f24a0c6..721b515 100644
--- a/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
+++ b/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
@@ -43,6 +43,7 @@ import type {
   ProcessNode,
 } from '@/api/client';
 import ProcessTopologyGraph from '@/layer/profiling/ProcessTopologyGraph.vue';
+import Icon from '@/components/icons/Icon.vue';
 
 const route = useRoute();
 const layerKey = computed(() => String(route.params.layerKey ?? ''));
@@ -212,11 +213,21 @@ function fmtTime(ms: number): string {
 
       <div class="side-head between">
         <span>Network tasks</span>
-        <button
-          class="btn-new"
-          :disabled="!selectedInstanceId"
-          @click="showNewTask = true"
-        >+ New Task</button>
+        <div class="side-head-actions">
+          <button
+            class="btn-refresh"
+            :class="{ spinning: tasksLoading }"
+            :disabled="!selectedInstanceId || tasksLoading"
+            :title="!selectedInstanceId ? 'Pick an instance' : tasksLoading ? 
'Refreshing…' : 'Refresh task list'"
+            aria-label="Refresh task list"
+            @click="refreshTasks"
+          ><Icon name="refresh" :size="11" /></button>
+          <button
+            class="btn-new"
+            :disabled="!selectedInstanceId"
+            @click="showNewTask = true"
+          >+ New Task</button>
+        </div>
       </div>
       <div v-if="tasksError" class="side-err">{{ tasksError }}</div>
       <div v-else-if="tasksLoading && !tasks.length" 
class="side-empty">Loading…</div>
@@ -399,6 +410,28 @@ function fmtTime(ms: number): string {
   color: var(--sw-fg-1);
   cursor: pointer;
 }
+.side-head-actions { display: inline-flex; align-items: center; gap: 6px; }
+.btn-refresh {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  padding: 2px 6px;
+  border-radius: 3px;
+  border: 1px solid var(--sw-line-2);
+  background: var(--sw-bg-1);
+  color: var(--sw-fg-1);
+  cursor: pointer;
+  line-height: 0;
+}
+.btn-refresh:hover:not(:disabled) { border-color: var(--sw-accent); color: 
var(--sw-accent); }
+.btn-refresh:disabled { opacity: 0.5; cursor: not-allowed; }
+.btn-refresh.spinning :deep(svg) {
+  animation: prof-refresh-spin 1.6s linear infinite;
+  transform-origin: 50% 50%;
+}
+@keyframes prof-refresh-spin {
+  to { transform: rotate(360deg); }
+}
 .btn-new:hover:not(:disabled) {
   border-color: var(--sw-accent);
   color: var(--sw-accent);
diff --git a/apps/ui/src/layer/profiling/LayerPprofProfilingView.vue 
b/apps/ui/src/layer/profiling/LayerPprofProfilingView.vue
index 5018848..16082a6 100644
--- a/apps/ui/src/layer/profiling/LayerPprofProfilingView.vue
+++ b/apps/ui/src/layer/profiling/LayerPprofProfilingView.vue
@@ -31,6 +31,7 @@ import { useSelectedService } from 
'@/layer/useSelectedService';
 import { bffClient } from '@/api/client';
 import type { PprofTask, PprofTree, ProfileAnalyzationTree } from 
'@/api/client';
 import ProfileFlameGraph from '@/layer/profiling/ProfileFlameGraph.vue';
+import Icon from '@/components/icons/Icon.vue';
 
 const route = useRoute();
 const layerKey = computed(() => String(route.params.layerKey ?? ''));
@@ -180,7 +181,17 @@ function instanceName(id: string): string {
     <div class="pp-side">
       <div class="side-head between">
         <span>pprof tasks</span>
-        <button class="btn-new" :disabled="!serviceId" @click="showNewTask = 
true">+ New Task</button>
+        <div class="side-head-actions">
+          <button
+            class="btn-refresh"
+            :class="{ spinning: tasksLoading }"
+            :disabled="!serviceId || tasksLoading"
+            :title="!serviceId ? 'Pick a service first' : tasksLoading ? 
'Refreshing…' : 'Refresh task list'"
+            aria-label="Refresh task list"
+            @click="refreshTasks"
+          ><Icon name="refresh" :size="11" /></button>
+          <button class="btn-new" :disabled="!serviceId" @click="showNewTask = 
true">+ New Task</button>
+        </div>
       </div>
       <div v-if="tasksError" class="side-err">{{ tasksError }}</div>
       <div v-else-if="tasksLoading && !tasks.length" 
class="side-empty">Loading…</div>
@@ -334,6 +345,28 @@ function instanceName(id: string): string {
   color: var(--sw-fg-1);
   cursor: pointer;
 }
+.side-head-actions { display: inline-flex; align-items: center; gap: 6px; }
+.btn-refresh {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  padding: 2px 6px;
+  border-radius: 3px;
+  border: 1px solid var(--sw-line-2);
+  background: var(--sw-bg-1);
+  color: var(--sw-fg-1);
+  cursor: pointer;
+  line-height: 0;
+}
+.btn-refresh:hover:not(:disabled) { border-color: var(--sw-accent); color: 
var(--sw-accent); }
+.btn-refresh:disabled { opacity: 0.5; cursor: not-allowed; }
+.btn-refresh.spinning :deep(svg) {
+  animation: prof-refresh-spin 1.6s linear infinite;
+  transform-origin: 50% 50%;
+}
+@keyframes prof-refresh-spin {
+  to { transform: rotate(360deg); }
+}
 .btn-new:hover:not(:disabled) {
   border-color: var(--sw-accent);
   color: var(--sw-accent);
diff --git a/apps/ui/src/layer/profiling/LayerTraceProfilingView.vue 
b/apps/ui/src/layer/profiling/LayerTraceProfilingView.vue
index e67f041..f21946a 100644
--- a/apps/ui/src/layer/profiling/LayerTraceProfilingView.vue
+++ b/apps/ui/src/layer/profiling/LayerTraceProfilingView.vue
@@ -55,6 +55,7 @@ import type {
 import ProfileStackTable from '@/layer/profiling/ProfileStackTable.vue';
 import ProfileFlameGraph from '@/layer/profiling/ProfileFlameGraph.vue';
 import NativeTraceWaterfall from '@/layer/traces/NativeTraceWaterfall.vue';
+import Icon from '@/components/icons/Icon.vue';
 
 const route = useRoute();
 const layerKey = computed(() => String(route.params.layerKey ?? ''));
@@ -334,13 +335,24 @@ function fmtTime(ms: number): string {
       <div class="side-pane">
         <div class="side-head">
           <span>Profile tasks</span>
-          <button
-            type="button"
-            class="btn-new"
-            :disabled="!selectedId"
-            :title="selectedId ? 'Create a new profile task' : 'Pick a service 
first'"
-            @click="showNewTask = true"
-          >+ New Task</button>
+          <div class="side-head-actions">
+            <button
+              type="button"
+              class="btn-refresh"
+              :class="{ spinning: tasksLoading }"
+              :disabled="!selectedId || tasksLoading"
+              :title="!selectedId ? 'Pick a service first' : tasksLoading ? 
'Refreshing…' : 'Refresh task list'"
+              aria-label="Refresh task list"
+              @click="refreshTasks"
+            ><Icon name="refresh" :size="11" /></button>
+            <button
+              type="button"
+              class="btn-new"
+              :disabled="!selectedId"
+              :title="selectedId ? 'Create a new profile task' : 'Pick a 
service first'"
+              @click="showNewTask = true"
+            >+ New Task</button>
+          </div>
         </div>
         <div v-if="tasksError" class="side-err">{{ tasksError }}</div>
         <div v-else-if="tasksLoading && !tasks.length" 
class="side-empty">Loading…</div>
@@ -618,6 +630,28 @@ function fmtTime(ms: number): string {
   color: var(--sw-fg-1);
   cursor: pointer;
 }
+.side-head-actions { display: inline-flex; align-items: center; gap: 6px; }
+.btn-refresh {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  padding: 2px 6px;
+  border-radius: 3px;
+  border: 1px solid var(--sw-line-2);
+  background: var(--sw-bg-1);
+  color: var(--sw-fg-1);
+  cursor: pointer;
+  line-height: 0;
+}
+.btn-refresh:hover:not(:disabled) { border-color: var(--sw-accent); color: 
var(--sw-accent); }
+.btn-refresh:disabled { opacity: 0.5; cursor: not-allowed; }
+.btn-refresh.spinning :deep(svg) {
+  animation: prof-refresh-spin 1.6s linear infinite;
+  transform-origin: 50% 50%;
+}
+@keyframes prof-refresh-spin {
+  to { transform: rotate(360deg); }
+}
 .btn-new:hover:not(:disabled) {
   border-color: var(--sw-accent);
   color: var(--sw-accent);

Reply via email to