This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch refactor/decompose-large-files
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit ff8d323faebf81ea4e5ebdab35ba24307e28c2e6
Author: Wu Sheng <[email protected]>
AuthorDate: Fri Jun 26 23:52:08 2026 +0800

    fix(profiling,logs,alarms): reviewer-found bugs + create-flow consistency
    
    EndpointCombo now mirrors the parent's selected prop into its input, so a 
parent-side clear/change resets the displayed text — Logs and Browser Errors no 
longer show a stale endpoint/page after a service switch; Logs also resets its 
endpoint search keyword on service change.
    
    Logs "Run query" now refetches the facet sample too, not just the rows 
(facets carry a 30s staleTime, so level counts no longer lag the stream).
    
    Trace profiling's create modal reuses the shared EndpointCombo 
(search-and-select) instead of a separate input + dropdown; network profiling's 
instance pick moves inside the New Task modal — the create target is chosen 
there with Create gated on it — instead of sitting on the page.
    
    The alarm timeline's selection band is a neutral light tint instead of 
orange (which muddied the red/green firing/recovered bars), and 
ToolboxComponent is registered to silence ECharts' brush warning.
---
 apps/ui/src/components/charts/AlarmsTimeline.vue   |  4 ++-
 apps/ui/src/layer/_shared/EndpointCombo.vue        |  5 +++-
 apps/ui/src/layer/logs/LayerLogsView.vue           | 15 +++++-----
 apps/ui/src/layer/logs/useLayerLogs.ts             |  1 +
 .../layer/profiling/LayerNetworkProfilingView.vue  | 35 ++++++++++++----------
 .../layer/profiling/NewTraceProfileTaskModal.vue   | 19 +++++-------
 6 files changed, 43 insertions(+), 36 deletions(-)

diff --git a/apps/ui/src/components/charts/AlarmsTimeline.vue 
b/apps/ui/src/components/charts/AlarmsTimeline.vue
index f92b148..62f347c 100644
--- a/apps/ui/src/components/charts/AlarmsTimeline.vue
+++ b/apps/ui/src/components/charts/AlarmsTimeline.vue
@@ -38,6 +38,7 @@ import {
   BrushComponent,
   GridComponent,
   MarkLineComponent,
+  ToolboxComponent,
   TooltipComponent,
 } from 'echarts/components';
 import { CanvasRenderer } from 'echarts/renderers';
@@ -50,6 +51,7 @@ echarts.use([
   BrushComponent,
   GridComponent,
   MarkLineComponent,
+  ToolboxComponent,
   TooltipComponent,
   CanvasRenderer,
 ]);
@@ -161,7 +163,7 @@ function buildOption(): echarts.EChartsCoreOption {
       xAxisIndex: 0,
       brushType: 'lineX',
       brushMode: 'single',
-      brushStyle: { color: 'rgba(249,115,22,0.18)', borderColor: 
'rgba(249,115,22,0.6)' },
+      brushStyle: { color: 'rgba(226,232,240,0.10)', borderColor: 
'rgba(226,232,240,0.7)' },
       transformable: false,
       throttleType: 'debounce',
       throttleDelay: 200,
diff --git a/apps/ui/src/layer/_shared/EndpointCombo.vue 
b/apps/ui/src/layer/_shared/EndpointCombo.vue
index 1d6a149..6ccea6d 100644
--- a/apps/ui/src/layer/_shared/EndpointCombo.vue
+++ b/apps/ui/src/layer/_shared/EndpointCombo.vue
@@ -32,7 +32,7 @@
 import { watch } from 'vue';
 import { useEndpointCombo } from '@/layer/_shared/useEndpointCombo';
 
-withDefaults(
+const props = withDefaults(
   defineProps<{
     endpoints: ReadonlyArray<{ id: string; name: string }>;
     selected: string | null;
@@ -51,6 +51,9 @@ const emit = defineEmits<{
 
 const combo = useEndpointCombo();
 watch(combo.query, (q) => emit('update:query', q));
+// Mirror the parent's selection into the input so a parent-side clear/change
+// resets the displayed text (setDisplay skips the search debounce).
+watch(() => props.selected, (sel) => combo.setDisplay(sel ?? ''));
 
 function pick(name: string): void {
   combo.setDisplay(name);
diff --git a/apps/ui/src/layer/logs/LayerLogsView.vue 
b/apps/ui/src/layer/logs/LayerLogsView.vue
index a3d9a7d..228ac3d 100644
--- a/apps/ui/src/layer/logs/LayerLogsView.vue
+++ b/apps/ui/src/layer/logs/LayerLogsView.vue
@@ -150,8 +150,9 @@ const { endpoints: endpointList, isFetching: 
endpointsLoading } = useLayerEndpoi
 // No endpoint auto-pick on Logs either — same reasoning as the
 // instance picker above. Default is `All`; operator narrows by hand.
 watch(serviceName, (next, prev) => {
-  if (prev !== undefined && next !== prev && selectedEndpoint.value) {
-    setSelectedEndpoint(null);
+  if (prev !== undefined && next !== prev) {
+    if (selectedEndpoint.value) setSelectedEndpoint(null);
+    endpointQuery.value = '';
   }
 });
 const selectedEndpointObj = computed(() =>
@@ -278,7 +279,7 @@ const { logs, total, isFetching, error, refetch } = 
useLayerLogs(layerKey, {
   enabled: hasQueried,
 });
 
-const { facets } = useLayerLogFacets(layerKey, {
+const { facets, refetch: refetchFacets } = useLayerLogFacets(layerKey, {
   service: aService,
   instanceId: aInstanceId,
   endpointId: aEndpointId,
@@ -290,15 +291,15 @@ const { facets } = useLayerLogFacets(layerKey, {
   enabled: hasQueried,
 });
 
-// Run-query handler mirrors the trace tab: refetch both the log
-// stream + the facet sample on demand. With most filters already
-// auto-refetching, this is the operator's "I'm done editing — refresh
-// now" affordance, identical voice to `LayerTracesView#runQuery`.
+// Run query refetches BOTH the log stream and the facet sample (level
+// counts) so they never diverge — facets carry a 30s staleTime, so an
+// unchanged-condition Run query needs an explicit refetch.
 function runQuery(): void {
   page.value = 1;
   hasQueried.value = true;
   applyConditions();
   void refetch();
+  void refetchFacets();
 }
 
 // ── Density histogram (60 bins). Loki/Datadog style: stacked bars
diff --git a/apps/ui/src/layer/logs/useLayerLogs.ts 
b/apps/ui/src/layer/logs/useLayerLogs.ts
index 5cba8f7..1ebe208 100644
--- a/apps/ui/src/layer/logs/useLayerLogs.ts
+++ b/apps/ui/src/layer/logs/useLayerLogs.ts
@@ -136,6 +136,7 @@ export function useLayerLogFacets(layerKey: Ref<string>, 
params: LogFacetParams)
     facets: computed(() => q.data.value ?? null),
     isFetching: q.isFetching,
     error: q.error,
+    refetch: q.refetch,
   };
 }
 
diff --git a/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue 
b/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
index 10bf4ee..47cca91 100644
--- a/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
+++ b/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
@@ -350,20 +350,8 @@ function fmtTime(ms: number): string {
 
 <template>
   <div class="sw-card net-shell">
-    <!-- Side: instance picker + tasks -->
+    <!-- Side: tasks (the create target instance is chosen inside New Task) -->
     <div class="net-side">
-      <div class="side-head">
-        <span>Instance</span>
-      </div>
-      <div class="picker">
-        <select v-model="selectedInstanceId" class="sel wide" 
:disabled="!instances.instances.value.length">
-          <option v-if="!instances.instances.value.length" :value="null">— no 
instances —</option>
-          <option v-for="inst in instances.instances.value" :key="inst.id" 
:value="inst.id">
-            {{ inst.name }}
-          </option>
-        </select>
-      </div>
-
       <div class="side-head between">
         <span>Network tasks</span>
         <div class="side-head-actions">
@@ -377,8 +365,8 @@ function fmtTime(ms: number): string {
           ><Icon name="refresh" :size="11" /></button>
           <button
             class="btn-new"
-            :disabled="!selectedInstanceId"
-            :title="!selectedInstanceId ? 'Pick an instance first' : 'Create a 
new network profile task'"
+            :disabled="!serviceId"
+            :title="!serviceId ? 'Pick a service first' : 'Create a new 
network profile task'"
             @click="showNewTask = true"
           >+ New Task</button>
         </div>
@@ -497,6 +485,16 @@ function fmtTime(ms: number): string {
         <button class="x" @click="showNewTask = false">×</button>
       </div>
       <div class="dlg-body">
+        <div class="field">
+          <label>Instance</label>
+          <select v-model="selectedInstanceId" class="sel wide" 
:disabled="!instances.instances.value.length">
+            <option v-if="!instances.instances.value.length" :value="null">— 
no instances —</option>
+            <option v-for="inst in instances.instances.value" :key="inst.id" 
:value="inst.id">{{ inst.name }}</option>
+          </select>
+        </div>
+        <div v-if="!instances.instances.value.length" class="banner err">
+          No instances available for this service — a network profile task 
cannot be created.
+        </div>
         <p class="hint">
           OAP captures one connection sample per matching rule. Leave URI
           empty to match any request; toggle 4xx/5xx to scope by status.
@@ -533,7 +531,12 @@ function fmtTime(ms: number): string {
       </div>
       <div class="dlg-foot">
         <button class="btn-secondary" @click="showNewTask = 
false">Cancel</button>
-        <button class="btn-primary" @click="submitNewTask">Create task</button>
+        <button
+          class="btn-primary"
+          :disabled="!selectedInstanceId"
+          :title="selectedInstanceId ? 'Create the network profile task' : 'No 
instances available for this service'"
+          @click="submitNewTask"
+        >Create task</button>
       </div>
     </div>
   </div>
diff --git a/apps/ui/src/layer/profiling/NewTraceProfileTaskModal.vue 
b/apps/ui/src/layer/profiling/NewTraceProfileTaskModal.vue
index 1b79741..47ad5fe 100644
--- a/apps/ui/src/layer/profiling/NewTraceProfileTaskModal.vue
+++ b/apps/ui/src/layer/profiling/NewTraceProfileTaskModal.vue
@@ -25,6 +25,7 @@
 <script setup lang="ts">
 import { reactive, watch } from 'vue';
 import { useEscapeToClose } from '@/components/primitives/useEscapeToClose';
+import EndpointCombo from '@/layer/_shared/EndpointCombo.vue';
 
 export interface EndpointPick {
   id: string;
@@ -116,18 +117,14 @@ function submit(): void {
       <div class="dlg-body">
         <div class="field">
           <label>Endpoint name</label>
-          <input
-            :value="keyword"
-            placeholder="Type to search…"
-            class="ti-input wide"
-            @input="(ev: Event) => emit('update:keyword', (ev.target as 
HTMLInputElement).value)"
+          <EndpointCombo
+            :endpoints="endpoints"
+            :selected="newTask.endpointName || null"
+            placeholder="(any)"
+            @update:query="(q: string) => emit('update:keyword', q)"
+            @pick="(name: string) => (newTask.endpointName = name)"
+            @clear="newTask.endpointName = ''"
           />
-          <select v-model="newTask.endpointName" class="sel wide">
-            <option value="">(any)</option>
-            <option v-for="e in endpoints" :key="e.id" :value="e.name">
-              {{ e.name }}
-            </option>
-          </select>
         </div>
         <div class="field-row">
           <div class="field">

Reply via email to