@64mb/freelens-power 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +1 -1
  2. package/out/main/common/resource-search-kind-priority.js +23 -0
  3. package/out/main/common/resource-search-kind-priority.js.map +1 -0
  4. package/out/main/power-logs-query.js +1 -0
  5. package/out/main/power-logs-query.js.map +1 -1
  6. package/out/main/resource-search-database.js +7 -2
  7. package/out/main/resource-search-database.js.map +1 -1
  8. package/out/main/resource-search-worker.js +6 -2
  9. package/out/main/resource-search-worker.js.map +1 -1
  10. package/out/renderer/_virtual/_global-external_react.js +2 -1
  11. package/out/renderer/power-log-json-cell.js +74 -0
  12. package/out/renderer/power-log-json-cell.js.map +1 -0
  13. package/out/renderer/power-log-json-fields.js +144 -0
  14. package/out/renderer/power-log-json-fields.js.map +1 -0
  15. package/out/renderer/power-log-json.js +139 -20
  16. package/out/renderer/power-log-json.js.map +1 -1
  17. package/out/renderer/power-log-message-tokens.js +60 -0
  18. package/out/renderer/power-log-message-tokens.js.map +1 -0
  19. package/out/renderer/power-log-message.js +26 -0
  20. package/out/renderer/power-log-message.js.map +1 -0
  21. package/out/renderer/power-logs-sql-row.js +2 -2
  22. package/out/renderer/power-logs-sql-row.js.map +1 -1
  23. package/out/renderer/power-logs.css.js +36 -6
  24. package/out/renderer/power-logs.css.js.map +1 -1
  25. package/out/renderer/power-logs.js +249 -28
  26. package/out/renderer/power-logs.js.map +1 -1
  27. package/out/renderer/preferences/logs-preferences.js +159 -38
  28. package/out/renderer/preferences/logs-preferences.js.map +1 -1
  29. package/out/renderer/resource-search-model.js +2 -1
  30. package/out/renderer/resource-search-model.js.map +1 -1
  31. package/out/renderer/resource-search-sync.js +1 -1
  32. package/out/renderer/resource-search-sync.js.map +1 -1
  33. package/out/renderer/resource-search.js +84 -81
  34. package/out/renderer/resource-search.js.map +1 -1
  35. package/out/renderer/src/common/power-log-field-filters.js +92 -0
  36. package/out/renderer/src/common/power-log-field-filters.js.map +1 -0
  37. package/out/renderer/src/common/power-log-json-filter-sql.js +106 -0
  38. package/out/renderer/src/common/power-log-json-filter-sql.js.map +1 -0
  39. package/out/renderer/src/common/power-log-json-preset.js +36 -0
  40. package/out/renderer/src/common/power-log-json-preset.js.map +1 -0
  41. package/out/renderer/src/common/power-log-level.js +171 -0
  42. package/out/renderer/src/common/power-log-level.js.map +1 -0
  43. package/out/renderer/src/common/power-log-parser.js +7 -29
  44. package/out/renderer/src/common/power-log-parser.js.map +1 -1
  45. package/out/renderer/src/common/resource-search-kind-priority.js +28 -0
  46. package/out/renderer/src/common/resource-search-kind-priority.js.map +1 -0
  47. package/package.json +1 -1
@@ -8,8 +8,14 @@ const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runti
8
8
  const require_ai_diagnostic_handoff = require("./ai-diagnostic-handoff.js");
9
9
  const require_date_time = require("./src/common/date-time.js");
10
10
  const require_diagnostic_explain_button = require("./diagnostic-explain-button.js");
11
+ const require_power_log_field_filters = require("./src/common/power-log-field-filters.js");
12
+ const require_power_log_json_filter_sql = require("./src/common/power-log-json-filter-sql.js");
13
+ const require_power_log_json_preset = require("./src/common/power-log-json-preset.js");
11
14
  const require_power_logs_ai = require("./src/common/power-logs-ai.js");
15
+ const require_power_log_json_fields = require("./power-log-json-fields.js");
12
16
  const require_power_log_json = require("./power-log-json.js");
17
+ const require_power_log_message = require("./power-log-message.js");
18
+ const require_power_log_json_cell = require("./power-log-json-cell.js");
13
19
  const require_power_logs_css = require("./power-logs.css.js");
14
20
  const require_power_logs_client = require("./power-logs-client.js");
15
21
  const require_power_logs_collector = require("./power-logs-collector.js");
@@ -17,6 +23,7 @@ const require_power_logs_entrypoints = require("./power-logs-entrypoints.js");
17
23
  const require_power_logs_sql_row = require("./power-logs-sql-row.js");
18
24
  const require_resource_search_client = require("./resource-search-client.js");
19
25
  const require_power_logs_transport = require("./power-logs-transport.js");
26
+ let electron = require("electron");
20
27
  let node_crypto = require("node:crypto");
21
28
  //#region src/renderer/power-logs.tsx
22
29
  var Icon = (props) => /* @__PURE__ */ require_jsx_runtime.jsx("span", {
@@ -38,7 +45,7 @@ var examples = [
38
45
  ["Recent", "SELECT message, level, container, pod, timestamp FROM logs ORDER BY timestamp DESC NULLS LAST LIMIT 200"],
39
46
  ["Errors", "SELECT pod, container, count(*) AS errors FROM logs WHERE level IN ('error','fatal') GROUP BY pod, container ORDER BY errors DESC"],
40
47
  ["Timeline", "SELECT strftime('%Y-%m-%d %H:%M', timestamp / 1000, 'unixepoch') AS minute, level, count(*) AS lines FROM logs GROUP BY minute, level ORDER BY minute"],
41
- ["JSON", "SELECT pod, json_extract(fieldsJson, '$.message') AS message FROM logs WHERE fieldsJson IS NOT NULL LIMIT 100"]
48
+ ["JSON", "SELECT fieldsJson FROM logs WHERE fieldsJson IS NOT NULL LIMIT 100"]
42
49
  ];
43
50
  var sqlColumnWidth = (column) => column === "message" || column === "fieldsJson" ? 360 : column === "level" ? 90 : 200;
44
51
  function initialTarget() {
@@ -154,7 +161,7 @@ var SourcePicker = require__global_external_mobx_react.observer(() => {
154
161
  ]
155
162
  });
156
163
  });
157
- function LogRows({ records, selected, onSelect, follow, setFollow, compact }) {
164
+ function LogRows({ records, loading, selected, onSelect, follow, setFollow, compact }) {
158
165
  const viewport = require__global_external_react.useRef(null);
159
166
  const [top, setTop] = require__global_external_react.useState(0);
160
167
  const [height, setHeight] = require__global_external_react.useState(400);
@@ -175,7 +182,7 @@ function LogRows({ records, selected, onSelect, follow, setFollow, compact }) {
175
182
  follow,
176
183
  compact
177
184
  ]);
178
- const rowHeight = compact ? 24 : 34;
185
+ const rowHeight = compact ? 20 : 34;
179
186
  const start = Math.max(0, Math.floor(top / rowHeight) - 5);
180
187
  const visible = records.slice(start, start + Math.ceil(height / rowHeight) + 10);
181
188
  return /* @__PURE__ */ require_jsx_runtime.jsx("div", {
@@ -184,6 +191,7 @@ function LogRows({ records, selected, onSelect, follow, setFollow, compact }) {
184
191
  className: "power-logs-stream",
185
192
  ref: viewport,
186
193
  "aria-label": "Log lines",
194
+ "aria-busy": loading,
187
195
  onScroll: (event) => {
188
196
  const el = event.currentTarget;
189
197
  setTop(el.scrollTop);
@@ -191,7 +199,7 @@ function LogRows({ records, selected, onSelect, follow, setFollow, compact }) {
191
199
  },
192
200
  children: records.length === 0 ? /* @__PURE__ */ require_jsx_runtime.jsx("div", {
193
201
  className: "power-logs-empty",
194
- children: "No matching log lines"
202
+ children: loading ? "Loading logs…" : "No matching log lines"
195
203
  }) : /* @__PURE__ */ require_jsx_runtime.jsx("div", {
196
204
  style: {
197
205
  height: records.length * rowHeight,
@@ -226,22 +234,21 @@ function LogRows({ records, selected, onSelect, follow, setFollow, compact }) {
226
234
  record.pod
227
235
  ]
228
236
  }),
229
- /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: record.message })
237
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_power_log_message.LogMessage, { text: record.message })
230
238
  ]
231
239
  }, record.key))
232
240
  })
233
241
  })
234
242
  });
235
243
  }
236
- function LogDetails({ record, jsonColumn, onClose }) {
244
+ function LogDetails({ record, jsonColumn, onClose, onFilter }) {
237
245
  const structuredFields = require__global_external_react.useMemo(() => {
238
246
  if (!record?.fieldsJson) return "";
239
- try {
240
- return JSON.stringify(JSON.parse(record.fieldsJson), null, 2);
241
- } catch {
242
- return record.fieldsJson;
243
- }
247
+ return require_power_log_json_fields.formatJsonLiteral(record.fieldsJson);
244
248
  }, [record?.fieldsJson]);
249
+ const fieldsEnabled = require__global_external_react.useMemo(() => !require_power_log_json_fields.jsonHasDuplicateKeys(record.fieldsJson ?? ""), [record.fieldsJson]);
250
+ const [copyFeedback, setCopyFeedback] = require__global_external_react.useState("");
251
+ require__global_external_react.useEffect(() => setCopyFeedback(""), [structuredFields]);
245
252
  return /* @__PURE__ */ require_jsx_runtime.jsxs("aside", {
246
253
  className: "power-logs-detail",
247
254
  "aria-label": "Log line details",
@@ -260,11 +267,70 @@ function LogDetails({ record, jsonColumn, onClose }) {
260
267
  className: "power-logs-muted",
261
268
  children: [record.pod && sourceKey(record), record.level].filter(Boolean).join(" · ")
262
269
  }),
263
- record.message && /* @__PURE__ */ require_jsx_runtime.jsx("pre", { children: record.message }),
264
- record.fieldsJson && /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [/* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: jsonColumn ? `Structured fields · ${jsonColumn}` : "Structured fields" }), /* @__PURE__ */ require_jsx_runtime.jsx(require_power_log_json.LogJson, { text: structuredFields })] })
270
+ record.message && /* @__PURE__ */ require_jsx_runtime.jsx("pre", { children: /* @__PURE__ */ require_jsx_runtime.jsx(require_power_log_message.LogMessage, { text: record.message }) }),
271
+ record.fieldsJson && /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
272
+ className: "power-logs-structured-heading",
273
+ children: [
274
+ /* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: jsonColumn ? `Structured fields · ${jsonColumn}` : "Structured fields" }),
275
+ /* @__PURE__ */ require_jsx_runtime.jsx("button", {
276
+ type: "button",
277
+ className: "power-logs-detail-copy",
278
+ "aria-label": "Copy structured JSON",
279
+ title: copyFeedback || "Copy structured JSON",
280
+ onClick: () => {
281
+ try {
282
+ electron.clipboard.writeText(structuredFields);
283
+ setCopyFeedback("Copied");
284
+ } catch {
285
+ setCopyFeedback("Unable to copy JSON");
286
+ }
287
+ },
288
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
289
+ material: copyFeedback === "Copied" ? "check" : "content_copy",
290
+ size: 16
291
+ })
292
+ }),
293
+ /* @__PURE__ */ require_jsx_runtime.jsx("span", {
294
+ className: "power-logs-muted",
295
+ "aria-live": "polite",
296
+ children: copyFeedback
297
+ })
298
+ ]
299
+ }), /* @__PURE__ */ require_jsx_runtime.jsx(require_power_log_json.LogJson, {
300
+ text: structuredFields,
301
+ fieldsEnabled,
302
+ Icon,
303
+ onFilter
304
+ })] })
265
305
  ]
266
306
  });
267
307
  }
308
+ var sameFieldFilter = (a, b) => a.operator === b.operator && a.path.length === b.path.length && a.path.every((part, index) => part === b.path[index]) && require_power_log_field_filters.jsonValuesEqual(a.value, b.value);
309
+ var fieldFilterLabel = (filter) => `${filter.path.reduce((path, part) => `${path}[${typeof part === "number" ? part : JSON.stringify(part)}]`, "$")} ${filter.operator === "eq" ? "=" : "!="} ${JSON.stringify(filter.value)}`;
310
+ function FieldFilters({ filters, sql = false, disabled = false, onRemove }) {
311
+ if (!filters.length) return null;
312
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
313
+ className: "power-logs-field-filters",
314
+ "aria-label": sql ? "SQL JSON filters" : "Live JSON filters",
315
+ children: [/* @__PURE__ */ require_jsx_runtime.jsxs("span", {
316
+ className: "power-logs-muted",
317
+ children: [sql ? "Result filters" : "JSON filters", " · AND"]
318
+ }), filters.map((filter, index) => {
319
+ const label = `${filter.column ? `${filter.column} ` : ""}${fieldFilterLabel(filter)}`;
320
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
321
+ type: "button",
322
+ disabled,
323
+ title: label,
324
+ "aria-label": `Remove JSON filter ${label}`,
325
+ onClick: () => onRemove(index),
326
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: label }), /* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
327
+ material: "close",
328
+ size: 14
329
+ })]
330
+ }, index);
331
+ })]
332
+ });
333
+ }
268
334
  function Workspace({ clusterId, contextId }) {
269
335
  const client = require__global_external_react.useMemo(() => require_power_logs_client.createPowerLogsClient({
270
336
  clusterId,
@@ -280,6 +346,10 @@ function Workspace({ clusterId, contextId }) {
280
346
  const [text, setText] = require__global_external_react.useState("");
281
347
  const [level, setLevel] = require__global_external_react.useState("");
282
348
  const [source, setSource] = require__global_external_react.useState(initialSource);
349
+ const [fieldFilters, setFieldFilters] = require__global_external_react.useState([]);
350
+ const matchFields = require__global_external_react.useMemo(() => require_power_log_field_filters.compileJsonFilters(fieldFilters), [fieldFilters]);
351
+ const parsedFields = require__global_external_react.useRef(/* @__PURE__ */ new WeakMap());
352
+ const [sqlFieldFilters, setSqlFieldFilters] = require__global_external_react.useState();
283
353
  const preferences = require_logs_preferences_store.LogsPreferencesStore.getInstanceOrCreate();
284
354
  const limits = require_power_logs_limits.normalizePowerLogsLimits(preferences.limits);
285
355
  require__global_external_react.useEffect(() => {
@@ -291,18 +361,32 @@ function Workspace({ clusterId, contextId }) {
291
361
  const [liveCoverage, setLiveCoverage] = require__global_external_react.useState();
292
362
  const [live, setLive] = require__global_external_react.useState(false);
293
363
  const [follow, setFollow] = require__global_external_react.useState(true);
294
- const [busy, setBusy] = require__global_external_react.useState(false);
364
+ const [busy, setBusy] = require__global_external_react.useState(() => Boolean(initialTarget()));
295
365
  const [captures, setCaptures] = require__global_external_react.useState([]);
296
- const [captureId, setCaptureId] = require__global_external_react.useState("");
366
+ const [captureId, updateCaptureId] = require__global_external_react.useState("");
297
367
  const captureSelection = require__global_external_react.useRef(captureId);
298
368
  captureSelection.current = captureId;
369
+ const setCaptureId = (id) => {
370
+ captureSelection.current = id;
371
+ updateCaptureId(id);
372
+ };
299
373
  const deleting = require__global_external_react.useRef(/* @__PURE__ */ new Set());
300
374
  const [deletingCount, setDeletingCount] = require__global_external_react.useState(0);
301
375
  const [capturing, setCapturing] = require__global_external_react.useState();
302
376
  const [capturePending, setCapturePending] = require__global_external_react.useState(false);
303
377
  const [captureMinutes, setCaptureMinutes] = require__global_external_react.useState(15);
304
378
  const [previous, setPrevious] = require__global_external_react.useState(false);
305
- const [sql, setSql] = require__global_external_react.useState(examples[0][1]);
379
+ const [sql, updateSql] = require__global_external_react.useState(examples[0][1]);
380
+ const sqlDraft = require__global_external_react.useRef(sql);
381
+ sqlDraft.current = sql;
382
+ const setSql = (query) => {
383
+ sqlDraft.current = query;
384
+ updateSql(query);
385
+ };
386
+ const captureKeys = require__global_external_react.useRef(/* @__PURE__ */ new Map());
387
+ require__global_external_react.useEffect(() => {
388
+ setSqlFieldFilters((current) => current && (current.rendered !== sql || current.captureId !== captureId) ? void 0 : current);
389
+ }, [sql, captureId]);
306
390
  const [result, setResult] = require__global_external_react.useState();
307
391
  const [resultEvidence, setResultEvidence] = require__global_external_react.useState();
308
392
  const [resultPage, setResultPage] = require__global_external_react.useState(0);
@@ -447,6 +531,7 @@ function Workspace({ clusterId, contextId }) {
447
531
  };
448
532
  require__global_external_react.useEffect(() => {
449
533
  setText("");
534
+ setFieldFilters([]);
450
535
  setSources([]);
451
536
  setTab("live");
452
537
  readHistory();
@@ -581,7 +666,7 @@ function Workspace({ clusterId, contextId }) {
581
666
  setCaptures((items) => [saved, ...items.filter((item) => item.id !== saved.id)]);
582
667
  setTab("captures");
583
668
  setSql(examples[0][1]);
584
- runQuery(saved, examples[0][1]);
669
+ applyPreset("Recent", saved, true);
585
670
  }
586
671
  } catch {
587
672
  if (alive.current) setError("The temporary capture could not be finalized");
@@ -623,11 +708,115 @@ function Workspace({ clusterId, contextId }) {
623
708
  }
624
709
  }
625
710
  };
626
- const filtered = require__global_external_react.useMemo(() => records.filter((record) => (!text || record.message.toLocaleLowerCase().includes(text.toLocaleLowerCase())) && (!level || record.level === level) && (!source || (source.startsWith("container:") ? record.container === source.slice(10) : sourceKey(record) === source))), [
711
+ const applyPreset = async (title, selectedCapture, execute = false) => {
712
+ const captured = selectedCapture ?? captures.find((item) => item.id === captureId);
713
+ if (queryRef.current || !selectedCapture && captureController.current || !captured || captured.state === "collecting" || deleting.current.has(captured.id)) return;
714
+ const draft = execute ? examples[0][1] : sqlDraft.current;
715
+ const id = (0, node_crypto.randomUUID)();
716
+ queryRef.current = id;
717
+ setQueryId(id);
718
+ setError("");
719
+ let prepared;
720
+ try {
721
+ let keys = captureKeys.current.get(captured.id);
722
+ if (!keys) {
723
+ const schema = await client.query(captured.id, id, require_power_log_json_preset.JSON_KEY_DISCOVERY_SQL);
724
+ if (schema.error) throw new Error(schema.error);
725
+ const keyColumn = schema.columns.indexOf("key");
726
+ keys = schema.truncated ? ["\0"] : keyColumn < 0 ? [] : schema.rows.map((row) => row[keyColumn]).filter((key) => typeof key === "string");
727
+ captureKeys.current.set(captured.id, keys);
728
+ if (captureKeys.current.size > 10) captureKeys.current.delete(captureKeys.current.keys().next().value);
729
+ }
730
+ if (!alive.current || queryRef.current !== id || captureSelection.current !== captured.id) return;
731
+ if (sqlDraft.current !== draft) {
732
+ setError("SQL draft changed while detecting JSON fields. Select the preset again to replace it.");
733
+ return;
734
+ }
735
+ prepared = require_power_log_json_preset.buildJsonProjectionSql(keys, { recent: title === "Recent" });
736
+ setSql(prepared);
737
+ setSelectedSqlRow(void 0);
738
+ } catch {
739
+ if (alive.current) setError("Could not detect JSON fields for this snapshot. Try the preset again.");
740
+ } finally {
741
+ if (queryRef.current === id) {
742
+ queryRef.current = "";
743
+ if (alive.current) setQueryId("");
744
+ }
745
+ }
746
+ if (prepared && execute && alive.current) runQuery(captured, prepared);
747
+ };
748
+ const addLiveFilter = (filter) => {
749
+ if (!require_power_log_field_filters.jsonValuesEqual(filter.value, filter.value)) {
750
+ setError("This JSON value is too large or unsupported for filtering");
751
+ return;
752
+ }
753
+ if (fieldFilters.some((current) => sameFieldFilter(current, filter))) return;
754
+ if (fieldFilters.length >= 32) {
755
+ setError("Up to 32 JSON filters can be combined");
756
+ return;
757
+ }
758
+ setFieldFilters([...fieldFilters, filter]);
759
+ };
760
+ const activeSqlFilters = sqlFieldFilters?.rendered === sql && sqlFieldFilters.captureId === captureId ? sqlFieldFilters : void 0;
761
+ const sqlFilteringDisabled = Boolean(queryId || capturePending || deleting.current.has(captureId) || !captures.some((capture) => capture.id === captureId && capture.state !== "collecting"));
762
+ const applySqlFilters = (filters) => {
763
+ if (queryRef.current || captureController.current || sqlFilteringDisabled) return;
764
+ if (!resultEvidence || resultEvidence.sql !== sql) {
765
+ setError("Run the edited SQL query before filtering its results");
766
+ return;
767
+ }
768
+ try {
769
+ const base = activeSqlFilters?.base ?? sql;
770
+ const query = require_power_log_json_filter_sql.buildJsonFilterSql(base, filters);
771
+ setSqlFieldFilters({
772
+ base,
773
+ rendered: query,
774
+ captureId,
775
+ filters
776
+ });
777
+ setSql(query);
778
+ runQuery(void 0, query);
779
+ } catch {
780
+ setError("This JSON filter cannot be added to the query. Check the field path and value size.");
781
+ }
782
+ };
783
+ const addSqlFilter = (column, filter) => {
784
+ const columnNames = result?.columns.map((name) => name.replace(/[A-Z]/g, (letter) => letter.toLowerCase())) ?? [];
785
+ if (!result?.columns.includes(column) || new Set(columnNames).size !== columnNames.length) {
786
+ setError("JSON filters require unique result column names, ignoring case");
787
+ return;
788
+ }
789
+ const filters = activeSqlFilters?.filters ?? [];
790
+ if (filters.some((current) => current.column === column && sameFieldFilter(current, filter))) return;
791
+ if (filters.length >= 32) {
792
+ setError("Up to 32 JSON filters can be combined");
793
+ return;
794
+ }
795
+ applySqlFilters([...filters, {
796
+ ...filter,
797
+ column
798
+ }]);
799
+ };
800
+ const matchesRecordFields = (record) => {
801
+ if (!fieldFilters.length) return true;
802
+ if (!parsedFields.current.has(record)) {
803
+ let fields;
804
+ try {
805
+ fields = JSON.parse(record.fieldsJson ?? "null");
806
+ } catch {
807
+ fields = void 0;
808
+ }
809
+ parsedFields.current.set(record, fields);
810
+ }
811
+ return matchFields(parsedFields.current.get(record));
812
+ };
813
+ const filtered = require__global_external_react.useMemo(() => records.filter((record) => (!text || record.message.toLocaleLowerCase().includes(text.toLocaleLowerCase())) && (!level || record.level === level) && (!source || (source.startsWith("container:") ? record.container === source.slice(10) : sourceKey(record) === source)) && matchesRecordFields(record)), [
627
814
  records,
628
815
  text,
629
816
  level,
630
- source
817
+ source,
818
+ fieldFilters,
819
+ matchFields
631
820
  ]);
632
821
  const chosenCapture = captures.find((item) => item.id === captureId);
633
822
  const collectionNotes = tab === "live" ? [.../* @__PURE__ */ new Set([...notes, ...live ? liveCoverage?.notes ?? [] : []])] : [];
@@ -764,7 +953,7 @@ function Workspace({ clusterId, contextId }) {
764
953
  if (!captureId && latest && !capturePending) {
765
954
  setCaptureId(latest.id);
766
955
  setSql(examples[0][1]);
767
- runQuery(latest, examples[0][1]);
956
+ applyPreset("Recent", latest, true);
768
957
  }
769
958
  refreshCaptures();
770
959
  },
@@ -1004,6 +1193,10 @@ function Workspace({ clusterId, contextId }) {
1004
1193
  })
1005
1194
  ]
1006
1195
  }),
1196
+ /* @__PURE__ */ require_jsx_runtime.jsx(FieldFilters, {
1197
+ filters: fieldFilters,
1198
+ onRemove: (index) => setFieldFilters(fieldFilters.filter((_, item) => item !== index))
1199
+ }),
1007
1200
  /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
1008
1201
  className: "power-logs-muted power-logs-status",
1009
1202
  role: "status",
@@ -1024,6 +1217,7 @@ function Workspace({ clusterId, contextId }) {
1024
1217
  className: "power-logs-workspace",
1025
1218
  children: [/* @__PURE__ */ require_jsx_runtime.jsx(LogRows, {
1026
1219
  records: filtered,
1220
+ loading: busy,
1027
1221
  selected: selected?.key,
1028
1222
  onSelect: setSelected,
1029
1223
  follow,
@@ -1031,7 +1225,8 @@ function Workspace({ clusterId, contextId }) {
1031
1225
  compact
1032
1226
  }), selected && /* @__PURE__ */ require_jsx_runtime.jsx(LogDetails, {
1033
1227
  record: selected,
1034
- onClose: () => setSelected(void 0)
1228
+ onClose: () => setSelected(void 0),
1229
+ onFilter: addLiveFilter
1035
1230
  })]
1036
1231
  })
1037
1232
  ] }) : /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [
@@ -1100,7 +1295,7 @@ function Workspace({ clusterId, contextId }) {
1100
1295
  const snapshot = captures.find((item) => item.id === value);
1101
1296
  if (snapshot) {
1102
1297
  setSql(examples[0][1]);
1103
- runQuery(snapshot, examples[0][1]);
1298
+ applyPreset("Recent", snapshot, true);
1104
1299
  }
1105
1300
  },
1106
1301
  options: [{
@@ -1160,7 +1355,8 @@ function Workspace({ clusterId, contextId }) {
1160
1355
  }),
1161
1356
  examples.map(([title, query]) => /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
1162
1357
  type: "button",
1163
- onClick: () => setSql(query),
1358
+ disabled: !!queryId || capturePending,
1359
+ onClick: () => title === "JSON" || title === "Recent" ? void applyPreset(title) : setSql(query),
1164
1360
  children: [
1165
1361
  /* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
1166
1362
  material: title === "Recent" ? "history" : title === "Errors" ? "error_outline" : title === "Timeline" ? "timeline" : "data_object",
@@ -1173,13 +1369,36 @@ function Workspace({ clusterId, contextId }) {
1173
1369
  }, title))
1174
1370
  ]
1175
1371
  }),
1372
+ /* @__PURE__ */ require_jsx_runtime.jsx(FieldFilters, {
1373
+ sql: true,
1374
+ filters: activeSqlFilters?.filters ?? [],
1375
+ disabled: sqlFilteringDisabled,
1376
+ onRemove: (index) => applySqlFilters((activeSqlFilters?.filters ?? []).filter((_, item) => item !== index))
1377
+ }),
1176
1378
  /* @__PURE__ */ require_jsx_runtime.jsx("textarea", {
1177
1379
  rows: 2,
1178
1380
  id: "power-logs-sql",
1179
1381
  value: sql,
1180
1382
  maxLength: 65536,
1181
1383
  onChange: (event) => setSql(event.target.value),
1182
- spellCheck: false
1384
+ spellCheck: false,
1385
+ onClick: (event) => {
1386
+ event.stopPropagation();
1387
+ if (event.detail >= 3) {
1388
+ event.preventDefault();
1389
+ window.getSelection()?.removeAllRanges();
1390
+ event.currentTarget.select();
1391
+ }
1392
+ },
1393
+ onMouseDown: (event) => {
1394
+ event.stopPropagation();
1395
+ if (event.detail >= 3) {
1396
+ event.preventDefault();
1397
+ window.getSelection()?.removeAllRanges();
1398
+ event.currentTarget.focus();
1399
+ event.currentTarget.select();
1400
+ }
1401
+ }
1183
1402
  }),
1184
1403
  result ? /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [
1185
1404
  /* @__PURE__ */ require_jsx_runtime.jsx("div", {
@@ -1227,10 +1446,11 @@ function Workspace({ clusterId, contextId }) {
1227
1446
  children: row.map((cell, j) => /* @__PURE__ */ require_jsx_runtime.jsx("td", {
1228
1447
  "data-column": result.columns[j],
1229
1448
  "data-level": result.columns[j] === "level" ? String(cell) : void 0,
1230
- children: /* @__PURE__ */ require_jsx_runtime.jsx("span", {
1231
- className: "power-logs-cell",
1232
- title: cell === null ? "NULL" : String(cell),
1233
- children: cell === null ? "NULL" : String(cell)
1449
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_power_log_json_cell.LogJsonCell, {
1450
+ value: cell === null ? null : String(cell),
1451
+ message: result.columns[j] === "message",
1452
+ Icon,
1453
+ onFilter: sqlFilteringDisabled ? void 0 : (filter) => addSqlFilter(result.columns[j], filter)
1234
1454
  })
1235
1455
  }, j))
1236
1456
  }, i)) })
@@ -1242,6 +1462,7 @@ function Workspace({ clusterId, contextId }) {
1242
1462
  }), selectedSqlRow && /* @__PURE__ */ require_jsx_runtime.jsx(LogDetails, {
1243
1463
  record: selectedSqlRow.record,
1244
1464
  jsonColumn: selectedSqlRow.jsonColumn,
1465
+ onFilter: !sqlFilteringDisabled && selectedSqlRow.jsonColumn ? (filter) => addSqlFilter(selectedSqlRow.jsonColumn, filter) : void 0,
1245
1466
  onClose: () => setSelectedSqlRow(void 0)
1246
1467
  })]
1247
1468
  }),