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 de081e9  ui: eBPF process picker — per-row expand/fold to surface full 
attributes
de081e9 is described below

commit de081e9c1abf7634658e96a47ac3a771ab3b97aa
Author: Wu Sheng <[email protected]>
AuthorDate: Wed May 20 08:32:49 2026 +0800

    ui: eBPF process picker — per-row expand/fold to surface full attributes
    
    The picker table rendered `name=value · name=value · …` into a single
    truncating Attributes column. Cells used `text-overflow: ellipsis`, so
    anything past the visible width was hidden — including the grep-load-
    bearing fields like `command_line`, `container.id`, and the agent UUIDs.
    The hover-title was the only escape hatch, which doesn't survive
    copy-paste and chops at 64 chars in some browsers anyway.
    
    Add a chevron column at the right of each row that toggles an inline
    detail panel directly under that row. Expanded state lives in a Set on
    the picker (O(1) toggle, survives picker open/close because state is
    component-scoped not URL-scoped).
    
    The expanded panel renders one key/value per row:
       Process / Service / Instance / Agent / Detect type — uncropped, mono
       Labels                                              — chip strip
       Attributes                                          — table of
                                                             <name, value>
                                                             pairs, each on
                                                             its own line
                                                             with `overflow-
                                                             wrap: anywhere`
                                                             so JVM command
                                                             lines wrap
                                                             inside the box
    
    Row chrome changes:
    - Add a 5th grid column (26px) for the caret button.
    - Move the row from `<label>` to `<div>` so the chevron button and the
      expanded panel can sit at the row level without the label hijacking
      every click as a checkbox toggle. Restore checkbox-toggle behaviour
      via an explicit `@click="toggleProcessId"` on the row, with
      `@click.stop` on the checkbox cell so the native @change is the only
      thing that fires when the input itself is hit (no double-toggle).
    - Caret rotates 180° on open via a CSS transition and uses
      `<Icon name="caret">` (project icon component, per CLAUDE.md).
---
 .../src/layer/profiling/LayerEBPFProfilingView.vue | 176 +++++++++++++++++++--
 1 file changed, 159 insertions(+), 17 deletions(-)

diff --git a/apps/ui/src/layer/profiling/LayerEBPFProfilingView.vue 
b/apps/ui/src/layer/profiling/LayerEBPFProfilingView.vue
index fb54af2..ab87a5a 100644
--- a/apps/ui/src/layer/profiling/LayerEBPFProfilingView.vue
+++ b/apps/ui/src/layer/profiling/LayerEBPFProfilingView.vue
@@ -111,6 +111,18 @@ const highlightTop = ref(true);
 // Process picker state
 const processSearch = ref('');
 const showProcessPicker = ref(false);
+// Per-row expand/fold for the process picker. Rows render the same
+// truncated three-column shape by default; expanding shows every
+// attribute uncropped (the most useful column — `command_line`,
+// `container.id`, agent metadata — is too wide to fit a single grid
+// cell). Tracked as a Set to keep toggle O(1).
+const expandedProcessIds = ref<Set<string>>(new Set());
+function toggleProcessExpanded(id: string): void {
+  const next = new Set(expandedProcessIds.value);
+  if (next.has(id)) next.delete(id);
+  else next.add(id);
+  expandedProcessIds.value = next;
+}
 
 // New task modal
 const showNewTask = ref(false);
@@ -509,25 +521,70 @@ function toggleNewTaskLabel(l: string): void {
             <div class="cc cc-name">Process</div>
             <div class="cc cc-inst">Instance</div>
             <div class="cc cc-attrs">Attributes</div>
+            <div class="cc cc-exp"></div>
           </div>
           <div v-if="!filteredProcesses.length" class="empty">No matches.</div>
-          <label
-            v-for="p in filteredProcesses"
-            :key="p.id"
-            class="pr"
-            :class="{ on: selectedProcessIds.includes(p.id) }"
-          >
-            <div class="cc cc-sel">
-              <input
-                type="checkbox"
-                :checked="selectedProcessIds.includes(p.id)"
-                @change="toggleProcessId(p.id)"
-              />
+          <template v-for="p in filteredProcesses" :key="p.id">
+            <div
+              class="pr"
+              :class="{ on: selectedProcessIds.includes(p.id) }"
+              @click="toggleProcessId(p.id)"
+            >
+              <div class="cc cc-sel" @click.stop>
+                <input
+                  type="checkbox"
+                  :checked="selectedProcessIds.includes(p.id)"
+                  :aria-label="`Pin process ${p.name}`"
+                  @change="toggleProcessId(p.id)"
+                />
+              </div>
+              <div class="cc cc-name" :title="p.name">{{ p.name }}</div>
+              <div class="cc cc-inst" :title="p.instanceName ?? ''">{{ 
p.instanceName }}</div>
+              <div class="cc cc-attrs" :title="attrLine(p)">{{ attrLine(p) 
}}</div>
+              <button
+                type="button"
+                class="cc cc-exp pr-caret"
+                :class="{ open: expandedProcessIds.has(p.id) }"
+                :aria-expanded="expandedProcessIds.has(p.id)"
+                :aria-label="expandedProcessIds.has(p.id) ? 'Collapse details' 
: 'Expand details'"
+                @click.stop="toggleProcessExpanded(p.id)"
+              >
+                <Icon name="caret" :size="10" />
+              </button>
+            </div>
+            <div v-if="expandedProcessIds.has(p.id)" class="pr-expand">
+              <dl class="pe-rows">
+                <div class="pe-row"><dt>Process</dt><dd class="mono">{{ p.name 
}}</dd></div>
+                <div v-if="p.serviceName" class="pe-row">
+                  <dt>Service</dt><dd class="mono">{{ p.serviceName }}</dd>
+                </div>
+                <div v-if="p.instanceName" class="pe-row">
+                  <dt>Instance</dt><dd class="mono">{{ p.instanceName }}</dd>
+                </div>
+                <div v-if="p.agentId" class="pe-row">
+                  <dt>Agent</dt><dd class="mono">{{ p.agentId }}</dd>
+                </div>
+                <div v-if="p.detectType" class="pe-row">
+                  <dt>Detect type</dt><dd class="mono">{{ p.detectType }}</dd>
+                </div>
+                <div v-if="(p.labels ?? []).length" class="pe-row">
+                  <dt>Labels</dt>
+                  <dd>
+                    <span v-for="l in p.labels" :key="l" class="pe-chip">{{ l 
}}</span>
+                  </dd>
+                </div>
+                <div v-if="(p.attributes ?? []).length" class="pe-row 
pe-attrs">
+                  <dt>Attributes</dt>
+                  <dd>
+                    <div v-for="a in p.attributes" :key="a.name" 
class="pe-attr">
+                      <span class="pe-attr-k">{{ a.name }}</span>
+                      <span class="pe-attr-v mono">{{ a.value }}</span>
+                    </div>
+                  </dd>
+                </div>
+              </dl>
             </div>
-            <div class="cc cc-name" :title="p.name">{{ p.name }}</div>
-            <div class="cc cc-inst" :title="p.instanceName ?? ''">{{ 
p.instanceName }}</div>
-            <div class="cc cc-attrs" :title="attrLine(p)">{{ attrLine(p) 
}}</div>
-          </label>
+          </template>
         </div>
       </div>
 
@@ -912,7 +969,7 @@ function toggleNewTaskLabel(l: string): void {
 .ph,
 .pr {
   display: grid;
-  grid-template-columns: 30px minmax(120px, 1.2fr) minmax(120px, 1fr) 
minmax(160px, 2fr);
+  grid-template-columns: 30px minmax(120px, 1.2fr) minmax(120px, 1fr) 
minmax(160px, 2fr) 26px;
   align-items: center;
   font-size: 11px;
 }
@@ -941,6 +998,91 @@ function toggleNewTaskLabel(l: string): void {
   overflow: hidden;
   text-overflow: ellipsis;
 }
+.pr-caret {
+  background: transparent;
+  border: none;
+  color: var(--sw-fg-3);
+  padding: 0;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  line-height: 0;
+}
+.pr-caret:hover { color: var(--sw-accent); }
+.pr-caret :deep(svg) {
+  transition: transform 0.15s ease;
+  transform-origin: 50% 50%;
+}
+.pr-caret.open :deep(svg) {
+  transform: rotate(180deg);
+  color: var(--sw-accent);
+}
+
+/* Expanded detail panel — full-width, sits directly under its row,
+ * shares the picker's vertical scroll. Mono fonts on values for the
+ * grep-friendly fields (`command_line`, `container.id`, agent UUIDs).
+ * Removes the truncating ellipsis: each attribute is on its own line
+ * with overflow-wrap so monstrous JVM command lines wrap inside the
+ * box instead of pushing past it. */
+.pr-expand {
+  background: var(--sw-bg-2);
+  border-bottom: 1px solid var(--sw-line);
+  padding: 8px 12px 10px 38px;  /* 38px = 30px sel-col + 8px row padding */
+  font-size: 11px;
+  color: var(--sw-fg-1);
+}
+.pe-rows {
+  margin: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.pe-row {
+  display: grid;
+  grid-template-columns: 110px 1fr;
+  gap: 12px;
+  align-items: baseline;
+}
+.pe-row dt {
+  margin: 0;
+  color: var(--sw-fg-3);
+  font-weight: 600;
+  letter-spacing: 0.02em;
+}
+.pe-row dd {
+  margin: 0;
+  color: var(--sw-fg-0);
+  overflow-wrap: anywhere;
+  word-break: break-all;
+}
+.pe-row .mono { font-family: var(--sw-mono, monospace); }
+.pe-chip {
+  display: inline-block;
+  padding: 1px 6px;
+  border-radius: 3px;
+  background: var(--sw-bg-3, var(--sw-bg-1));
+  border: 1px solid var(--sw-line-2);
+  color: var(--sw-fg-1);
+  margin: 0 4px 4px 0;
+  font-size: 10.5px;
+}
+.pe-attrs dd { display: flex; flex-direction: column; gap: 2px; }
+.pe-attr {
+  display: grid;
+  grid-template-columns: minmax(130px, max-content) 1fr;
+  gap: 10px;
+  align-items: baseline;
+}
+.pe-attr-k {
+  color: var(--sw-fg-2);
+  font-family: var(--sw-mono, monospace);
+}
+.pe-attr-v {
+  color: var(--sw-fg-0);
+  overflow-wrap: anywhere;
+  word-break: break-all;
+}
 
 .result {
   flex: 1 1 0;

Reply via email to