{{ m.label }}
@@ -81,38 +82,59 @@ const metrics = computed(() => {
Waiting for stats...
+
+
+ {{ m.label }}
+ {{ m.value }}
+ {{ m.sub }}
+
+
diff --git a/ui/framework/src/index.ts b/ui/framework/src/index.ts
index 3d9274d..a9742c8 100644
--- a/ui/framework/src/index.ts
+++ b/ui/framework/src/index.ts
@@ -7,6 +7,7 @@ export { useDataSource } from './composables/useDataSource'
// Components
export { default as Panel } from './components/Panel.vue'
export { default as LayoutGrid } from './components/LayoutGrid.vue'
+export { default as ResizeHandle } from './components/ResizeHandle.vue'
// Renderers
export { default as LogRenderer } from './renderers/LogRenderer.vue'
diff --git a/ui/framework/src/renderers/TableRenderer.vue b/ui/framework/src/renderers/TableRenderer.vue
index 0feb3b2..d4c3d69 100644
--- a/ui/framework/src/renderers/TableRenderer.vue
+++ b/ui/framework/src/renderers/TableRenderer.vue
@@ -76,6 +76,7 @@ const sorted = computed(() => {
table {
width: 100%;
border-collapse: collapse;
+ table-layout: fixed;
}
th {
@@ -89,7 +90,6 @@ th {
border-bottom: var(--panel-border);
cursor: pointer;
user-select: none;
- white-space: nowrap;
}
th:hover {
@@ -104,7 +104,10 @@ th:hover {
td {
padding: var(--space-1) var(--space-3);
border-bottom: 1px solid var(--surface-3);
- white-space: nowrap;
+ white-space: normal;
+ word-break: break-word;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
tr:hover td {
diff --git a/ui/framework/src/renderers/TimeSeriesRenderer.vue b/ui/framework/src/renderers/TimeSeriesRenderer.vue
index b0baf75..c664ffb 100644
--- a/ui/framework/src/renderers/TimeSeriesRenderer.vue
+++ b/ui/framework/src/renderers/TimeSeriesRenderer.vue
@@ -22,6 +22,7 @@ const props = withDefaults(defineProps<{
})
const container = ref
(null)
+const zoomed = ref(false)
let chart: uPlot | null = null
function buildOpts(): uPlot.Options {
@@ -40,25 +41,66 @@ function buildOpts(): uPlot.Options {
height: container.value?.clientHeight ?? 200,
series: seriesOpts,
axes: [
- { stroke: '#555568', grid: { stroke: '#2e2e3822' } },
- { stroke: '#555568', grid: { stroke: '#2e2e3822' } },
+ {
+ stroke: '#555568',
+ grid: { stroke: '#2e2e3822' },
+ size: 40,
+ font: '10px monospace',
+ ticks: { size: 3 },
+ },
+ {
+ stroke: '#555568',
+ grid: { stroke: '#2e2e3822' },
+ size: 35,
+ font: '10px monospace',
+ ticks: { size: 3 },
+ },
],
cursor: { show: true },
- legend: { show: true },
+ legend: { show: true, live: false },
+ padding: [8, 8, 0, 0],
+ hooks: {
+ setScale: [(_self: uPlot, scaleKey: string) => {
+ if (scaleKey === 'x') zoomed.value = true
+ }],
+ },
}
}
+function resetZoom() {
+ if (!chart) return
+ const data = chart.data
+ if (data && data[0] && data[0].length > 0) {
+ const min = data[0][0]
+ const max = data[0][data[0].length - 1]
+ chart.setScale('x', { min, max })
+ }
+ zoomed.value = false
+}
+
+function getLegendHeight(): number {
+ if (!container.value) return 0
+ const legend = container.value.querySelector('.u-legend') as HTMLElement | null
+ return legend ? legend.offsetHeight : 0
+}
+
function createChart() {
if (!container.value) return
if (chart) chart.destroy()
chart = new uPlot(buildOpts(), props.data, container.value)
+ // Refit after legend renders
+ nextTick(() => resize())
}
function resize() {
if (!chart || !container.value) return
+ const legendH = getLegendHeight()
+ const availableH = container.value.clientHeight
+ // uPlot height = canvas height (chart sets total = canvas + legend)
+ const chartH = Math.max(60, availableH - legendH)
chart.setSize({
width: container.value.clientWidth,
- height: container.value.clientHeight,
+ height: chartH,
})
}
@@ -83,19 +125,74 @@ onMounted(() => {
-
+