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">
