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);