diff --git a/assets/components.d.ts b/assets/components.d.ts index 03f98cc2..9ad22a46 100644 --- a/assets/components.d.ts +++ b/assets/components.d.ts @@ -162,10 +162,12 @@ declare module 'vue' { PageWithLinks: typeof import('./components/PageWithLinks.vue')['default'] 'Ph:arrowsMerge': typeof import('~icons/ph/arrows-merge')['default'] 'Ph:boundingBoxFill': typeof import('~icons/ph/bounding-box-fill')['default'] + 'Ph:caretRight': typeof import('~icons/ph/caret-right')['default'] 'Ph:circlesFour': typeof import('~icons/ph/circles-four')['default'] 'Ph:command': typeof import('~icons/ph/command')['default'] 'Ph:computerTower': typeof import('~icons/ph/computer-tower')['default'] 'Ph:controlBold': typeof import('~icons/ph/control-bold')['default'] + 'Ph:database': typeof import('~icons/ph/database')['default'] 'Ph:dotsThreeVerticalBold': typeof import('~icons/ph/dots-three-vertical-bold')['default'] 'Ph:fileSql': typeof import('~icons/ph/file-sql')['default'] 'Ph:globeSimple': typeof import('~icons/ph/globe-simple')['default'] diff --git a/assets/components/LogViewer/LogAnalytics.vue b/assets/components/LogViewer/LogAnalytics.vue index 3a21c4b0..621b5760 100644 --- a/assets/components/LogViewer/LogAnalytics.vue +++ b/assets/components/LogViewer/LogAnalytics.vue @@ -1,37 +1,92 @@ @@ -42,11 +97,15 @@ import { type Table } from "@apache-arrow/esnext-esm"; const { container } = defineProps<{ container: Container }>(); const query = ref("SELECT * FROM logs LIMIT 100"); const error = ref(null); -const debouncedQuery = debouncedRef(query, 500); const evaluating = ref(false); const pageLimit = 1000; const state = ref<"downloading" | "ready" | "initializing">("downloading"); const bytes = ref(0); +const columns = ref<{ name: string; type: string }[]>([]); +const queryEl = useTemplateRef("queryEl"); + +const runQuery = ref(query.value); +watchDebounced(query, (v) => (runQuery.value = v), { debounce: 500 }); const url = withBase( `/api/hosts/${container.host}/containers/${container.id}/logs?stdout=1&stderr=1&everything&jsonOnly`, @@ -93,6 +152,12 @@ onMounted(async () => { `CREATE TABLE logs AS SELECT unnest(m) FROM read_json('logs.json', ignore_errors = true, format = 'newline_delimited', map_inference_threshold = -1)`, ); + const described = await conn.query<{ column_name: any; column_type: any }>(`DESCRIBE logs`); + columns.value = described.toArray().map((row) => ({ + name: String(row.column_name), + type: String(row.column_type), + })); + state.value = "ready"; } catch (e) { console.error(e); @@ -102,10 +167,54 @@ onMounted(async () => { } }); +const examples = computed(() => { + const names = columns.value.map((c) => c.name); + const pick = ["level", "severity", "lvl", "status"].find((c) => names.includes(c)) ?? names[0]; + const list: { key: string; sql: string; params?: Record }[] = [ + { key: "analytics.example_all", sql: "SELECT * FROM logs LIMIT 100" }, + { key: "analytics.example_count", sql: "SELECT count(*) AS total FROM logs" }, + ]; + if (pick) { + list.push({ + key: "analytics.example_group", + params: { column: pick }, + sql: `SELECT "${pick}", count(*) AS count FROM logs GROUP BY "${pick}" ORDER BY count DESC`, + }); + } + return list; +}); + +function run() { + if (state.value !== "ready") return; + runQuery.value = query.value; +} + +function applyExample(sql: string) { + query.value = sql; + nextTick(run); +} + +function insertColumn(name: string) { + const text = `"${name}"`; + const el = queryEl.value; + if (!el) { + query.value += text; + return; + } + const start = el.selectionStart ?? query.value.length; + const end = el.selectionEnd ?? start; + query.value = query.value.slice(0, start) + text + query.value.slice(end); + nextTick(() => { + el.focus(); + const pos = start + text.length; + el.setSelectionRange(pos, pos); + }); +} + const results = computedAsync( async () => { if (state.value === "ready") { - return await conn.query>(debouncedQuery.value); + return await conn.query>(runQuery.value); } else { return empty; } diff --git a/assets/components/LogViewer/SQLTable.vue b/assets/components/LogViewer/SQLTable.vue index f88357bb..f86123e5 100644 --- a/assets/components/LogViewer/SQLTable.vue +++ b/assets/components/LogViewer/SQLTable.vue @@ -1,28 +1,39 @@