@64mb/freelens-power 2.1.1 → 2.2.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.
- package/README.md +4 -4
- package/out/main/common/power-logs.js.map +1 -1
- package/out/main/power-logs-child.js +3 -3
- package/out/main/power-logs-child.js.map +1 -1
- package/out/main/power-logs-database.js +165 -7
- package/out/main/power-logs-database.js.map +1 -1
- package/out/main/power-logs-ipc.js +12 -3
- package/out/main/power-logs-ipc.js.map +1 -1
- package/out/main/power-logs-worker.js +29 -7
- package/out/main/power-logs-worker.js.map +1 -1
- package/out/renderer/ai-diagnostic-handoff.js +2 -2
- package/out/renderer/ai-diagnostic-handoff.js.map +1 -1
- package/out/renderer/ai-extras.js +1 -1
- package/out/renderer/ai-extras.js.map +1 -1
- package/out/renderer/ai.js +4 -1
- package/out/renderer/ai.js.map +1 -1
- package/out/renderer/diagnostics-screen-finding.js +51 -0
- package/out/renderer/diagnostics-screen-finding.js.map +1 -0
- package/out/renderer/diagnostics.css.js +1 -1
- package/out/renderer/diagnostics.css.js.map +1 -1
- package/out/renderer/diagnostics.js +12 -0
- package/out/renderer/diagnostics.js.map +1 -1
- package/out/renderer/index.js +2 -0
- package/out/renderer/index.js.map +1 -1
- package/out/renderer/pod-details-enhancements.js +106 -0
- package/out/renderer/pod-details-enhancements.js.map +1 -0
- package/out/renderer/power-log-json-cell.js +15 -14
- package/out/renderer/power-log-json-cell.js.map +1 -1
- package/out/renderer/power-log-json.js +14 -6
- package/out/renderer/power-log-json.js.map +1 -1
- package/out/renderer/power-log-sql-editor.js +31 -2
- package/out/renderer/power-log-sql-editor.js.map +1 -1
- package/out/renderer/power-logs-client.js +5 -0
- package/out/renderer/power-logs-client.js.map +1 -1
- package/out/renderer/power-logs-entrypoints.js +10 -36
- package/out/renderer/power-logs-entrypoints.js.map +1 -1
- package/out/renderer/power-logs.css.js +9 -2
- package/out/renderer/power-logs.css.js.map +1 -1
- package/out/renderer/power-logs.js +220 -91
- package/out/renderer/power-logs.js.map +1 -1
- package/out/renderer/preferences/logs-preferences.js +1 -1
- package/out/renderer/preferences/logs-preferences.js.map +1 -1
- package/out/renderer/relationship-map-finding.js +53 -0
- package/out/renderer/relationship-map-finding.js.map +1 -0
- package/out/renderer/resource-relationships.css.js +6 -0
- package/out/renderer/resource-relationships.css.js.map +1 -1
- package/out/renderer/resource-relationships.js +50 -5
- package/out/renderer/resource-relationships.js.map +1 -1
- package/out/renderer/resource-search.js +13 -3
- package/out/renderer/resource-search.js.map +1 -1
- package/out/renderer/src/common/gateway-relationships.js +8 -7
- package/out/renderer/src/common/gateway-relationships.js.map +1 -1
- package/out/renderer/src/common/power-log-json-filter-sql.js +12 -0
- package/out/renderer/src/common/power-log-json-filter-sql.js.map +1 -1
- package/out/renderer/src/common/power-log-json-preset.js +2 -2
- package/out/renderer/src/common/power-log-json-preset.js.map +1 -1
- package/out/renderer/src/common/power-logs.js.map +1 -1
- package/out/renderer/src/common/resource-relationships.js +10 -8
- package/out/renderer/src/common/resource-relationships.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,18 +12,18 @@ Power tools and personal dashboards for Freelens. Built for Freelens 1.x and tes
|
|
|
12
12
|
- **Power Dashboard:** reorderable XS/S/M/L/XL website cards in a responsive grid, cached pages, loading skeletons, interactive element selection, expansion, pinned floating windows, shared website sessions, per-card 10-second refresh including pinned windows, and optional background refresh
|
|
13
13
|
- **Monitoring:** optional CPU and memory trends, allocation rings, pinned tooltips, and node/pod charts using metrics-server and Kubernetes APIs
|
|
14
14
|
- **Resource diagnostics:** CPU/memory consumers, grouped warning timelines, Pending and Not ready triage, Service/HPA checks, operational resource and namespace summaries, scoped AI handoff, and incident-summary copying
|
|
15
|
-
- **Power Logs:** opt-in colored workload logs with structured fields, source and level filters, bounded live collection, temporary SQLite
|
|
16
|
-
- **Resource relationships:** a focused Service graph connects Ingress and installed Gateway API routes/listeners to selected Pods, published endpoints, owners and node references, with acceptance and cross-namespace reference evidence, resource links and saved upstream visibility
|
|
15
|
+
- **Power Logs:** opt-in colored workload logs with structured fields, source and level filters, bounded live collection, temporary or pinned SQLite snapshots, cancellable read-only SQL, compact density, configurable limits, NDJSON export and reviewable AI context with SQL drafts; native Pod logs remain available. JSON details support syntax coloring, value copying and AND field filters in Live view and SQL; SQL presets retain original JSON field names, and five preset selectors control log limits
|
|
16
|
+
- **Resource relationships:** a focused Service graph connects Ingress and installed Gateway API routes/listeners to selected Pods, published endpoints, owners and node references, with acceptance and cross-namespace reference evidence, resource links, explicit expansion of all available graph resources and saved upstream visibility
|
|
17
17
|
- **Fast resource search:** double Shift searches all discoverable resource types, including Secrets, ConfigMaps and custom resources, through a persistent local metadata index with gentle background refresh, type and namespace filters, name-part and typo matching, resource links and explicit AI attachment; search and background indexing can be disabled together in Monitoring settings
|
|
18
18
|
- **Local metric history:** opt-in SQLite storage, up to seven days within a 100 MiB budget, with aggregated ranges and explicit collection gaps
|
|
19
|
-
- **AI assistant:** native Codex or Claude CLI, per-cluster conversations, read-only API discovery and resource summaries, model/effort/speed selection, editable prompts, quick-start commands, Markdown, cancellable replies, context preview, optional terminal attachments, resource links, resizable pinned chat, and a switch to disable all AI features
|
|
19
|
+
- **AI assistant:** native Codex or Claude CLI, per-cluster conversations, read-only API discovery and resource summaries, model/effort/speed selection, editable prompts, quick-start commands, Markdown, cancellable replies, context preview, optional terminal attachments, automatic visible Relationships/Consumers/Timeline snapshots when no attachment exists, resource links, resizable pinned chat, and a switch to disable all AI features
|
|
20
20
|
- **Read-only UI:** hides common mutation actions without changing Kubernetes permissions
|
|
21
21
|
- **Master switch:** restores the native interface while preserving all settings
|
|
22
22
|
- **Settings:** native switches, compact dashboard editors, and JSON backup and restore of cards and prompt shortcuts
|
|
23
23
|
|
|
24
24
|
AI tool results exclude environment values and Secret payloads; automatic log summaries contain numeric issue counters. Power Logs lines and SQL results attach to a reviewable context block when the AI action is clicked. Drafted SQL is inserted into the editor without execution. Terminal output is sent only when explicitly attached; review its masked preview before sending. Websites run in isolated Electron views. Every dashboard card also opens in your browser.
|
|
25
25
|
|
|
26
|
-
Power Logs captures stay on the local disk for one hour by default, with configurable retention, and are removed when the feature stops. Collection follows current Kubernetes permissions and retained container logs. Large workloads use rotating bounded tails, so busy sources can have gaps; coverage and partial captures are reported in the interface.
|
|
26
|
+
Power Logs captures stay on the local disk for one hour by default, with configurable retention, and are removed when the feature stops unless pinned. Pinned snapshots survive restarts and remain until manually deleted; unpinning starts a new retention period. Snapshot deletion and pinning require confirmation. Temporary and pinned snapshot pools each use the configured capture count limit, with the same combined limit per cluster context. Collection follows current Kubernetes permissions and retained container logs. Large workloads use rotating bounded tails, so busy sources can have gaps; coverage and partial captures are reported in the interface.
|
|
27
27
|
|
|
28
28
|
Monitoring history is collected while Freelens is running and the cluster view is active. Cluster and node series are retained automatically when enabled. History cannot backfill periods without collection. Timelines combine retained Kubernetes events with changes observed while the diagnostics page is open. Local Pending checks explain available evidence and do not reproduce the full scheduler.
|
|
29
29
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-logs.js","names":[],"sources":["../../../src/common/power-logs.ts"],"sourcesContent":["export const POWER_LOGS_CHANNEL = \"freelens-power:logs\";\nexport const POWER_LOGS_DEFAULT_LIMITS = {\n liveRows: 10000,\n liveBytes: 16 * 1024 * 1024,\n batchRows: 1000,\n batchBytes: 2 * 1024 * 1024,\n recordBytes: 64 * 1024,\n captureBytes: 256 * 1024 * 1024,\n captureRows: 1000000,\n captures: 3,\n sources: 200,\n resultRows: 1000,\n resultBytes: 2 * 1024 * 1024,\n queryMs: 15000,\n ttlMs: 60 * 60 * 1000,\n historyLines: 1000,\n historyBytes: 1024 * 1024,\n captureSourceLines: 10000,\n captureSourceBytes: 8 * 1024 * 1024,\n requestsPerSecond: 2,\n bytesPerSecond: 1024 * 1024,\n historyConcurrency: 2,\n liveConcurrency: 4,\n} as const;\n\nexport type PowerLogsLimitKey = keyof typeof POWER_LOGS_DEFAULT_LIMITS;\nexport type PowerLogsLimits = { [Key in PowerLogsLimitKey]: number };\nexport const POWER_LOGS_LIMITS: PowerLogsLimits = { ...POWER_LOGS_DEFAULT_LIMITS };\n\nexport type LogLevel = \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\" | \"fatal\" | \"unknown\";\nexport type LogWorkloadKind = \"Pod\" | \"Deployment\" | \"DaemonSet\" | \"StatefulSet\" | \"ReplicaSet\" | \"Job\" | \"CronJob\";\nexport interface LogTarget {\n kind: LogWorkloadKind;\n namespace: string;\n name: string;\n uid: string;\n}\nexport interface LogSource {\n namespace: string;\n pod: string;\n podUid: string;\n container: string;\n previous?: boolean;\n}\nexport interface LogRecord {\n timestamp: number | null;\n pod: string;\n podUid: string;\n container: string;\n level: LogLevel;\n message: string;\n fieldsJson: string | null;\n previous?: boolean;\n}\nexport interface LogCapture {\n id: string;\n target: LogTarget;\n createdAt: number;\n expiresAt: number;\n state: \"collecting\" | \"ready\" | \"partial\";\n rows: number;\n bytes: number;\n notes: string[];\n lastSql?: string;\n}\nexport type LogCell = string | number | boolean | null;\nexport interface LogQueryResult {\n error?: string;\n columns: string[];\n rows: LogCell[][];\n truncated: boolean;\n elapsedMs: number;\n}\nexport interface LogPartition {\n clusterId: string;\n contextId: string;\n}\nexport type LogRequest = LogPartition &\n (\n | { operation: \"permit\"; lane: \"start\" | \"bytes\"; bytes?: number }\n | { operation: \"stream-open\"; streamId: string; follow: boolean }\n | { operation: \"stream-close\" | \"stream-renew\"; streamId: string }\n | { operation: \"create\"; target: LogTarget }\n | { operation: \"append\"; captureId: string; records: LogRecord[] }\n | { operation: \"finish\"; captureId: string; partial: boolean; notes: string[] }\n | { operation: \"list\" }\n | { operation: \"delete\"; captureId: string }\n | { operation: \"save-sql\"; captureId: string; sql: string }\n | { operation: \"query\"; captureId: string; queryId: string; sql: string }\n | { operation: \"cancel\"; queryId: string }\n | { operation: \"export\"; captureId: string }\n );\n"],"mappings":";AAAA,IAAa,qBAAqB;AAClC,IAAa,4BAA4B;CACvC,UAAU;CACV,WAAW,KAAK,OAAO;CACvB,WAAW;CACX,YAAY,IAAI,OAAO;CACvB,aAAa,KAAK;CAClB,cAAc,MAAM,OAAO;CAC3B,aAAa;CACb,UAAU;CACV,SAAS;CACT,YAAY;CACZ,aAAa,IAAI,OAAO;CACxB,SAAS;CACT,OAAO,OAAU;CACjB,cAAc;CACd,cAAc,OAAO;CACrB,oBAAoB;CACpB,oBAAoB,IAAI,OAAO;CAC/B,mBAAmB;CACnB,gBAAgB,OAAO;CACvB,oBAAoB;CACpB,iBAAiB;AACnB;AAIA,IAAa,oBAAqC,EAAE,GAAG,0BAA0B"}
|
|
1
|
+
{"version":3,"file":"power-logs.js","names":[],"sources":["../../../src/common/power-logs.ts"],"sourcesContent":["export const POWER_LOGS_CHANNEL = \"freelens-power:logs\";\nexport const POWER_LOGS_DEFAULT_LIMITS = {\n liveRows: 10000,\n liveBytes: 16 * 1024 * 1024,\n batchRows: 1000,\n batchBytes: 2 * 1024 * 1024,\n recordBytes: 64 * 1024,\n captureBytes: 256 * 1024 * 1024,\n captureRows: 1000000,\n captures: 3,\n sources: 200,\n resultRows: 1000,\n resultBytes: 2 * 1024 * 1024,\n queryMs: 15000,\n ttlMs: 60 * 60 * 1000,\n historyLines: 1000,\n historyBytes: 1024 * 1024,\n captureSourceLines: 10000,\n captureSourceBytes: 8 * 1024 * 1024,\n requestsPerSecond: 2,\n bytesPerSecond: 1024 * 1024,\n historyConcurrency: 2,\n liveConcurrency: 4,\n} as const;\n\nexport type PowerLogsLimitKey = keyof typeof POWER_LOGS_DEFAULT_LIMITS;\nexport type PowerLogsLimits = { [Key in PowerLogsLimitKey]: number };\nexport const POWER_LOGS_LIMITS: PowerLogsLimits = { ...POWER_LOGS_DEFAULT_LIMITS };\n\nexport type LogLevel = \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\" | \"fatal\" | \"unknown\";\nexport type LogWorkloadKind = \"Pod\" | \"Deployment\" | \"DaemonSet\" | \"StatefulSet\" | \"ReplicaSet\" | \"Job\" | \"CronJob\";\nexport interface LogTarget {\n kind: LogWorkloadKind;\n namespace: string;\n name: string;\n uid: string;\n}\nexport interface LogSource {\n namespace: string;\n pod: string;\n podUid: string;\n container: string;\n previous?: boolean;\n}\nexport interface LogRecord {\n timestamp: number | null;\n pod: string;\n podUid: string;\n container: string;\n level: LogLevel;\n message: string;\n fieldsJson: string | null;\n previous?: boolean;\n}\nexport interface LogCapture {\n id: string;\n target: LogTarget;\n createdAt: number;\n expiresAt: number;\n state: \"collecting\" | \"ready\" | \"partial\";\n rows: number;\n bytes: number;\n notes: string[];\n lastSql?: string;\n persistent?: boolean;\n}\nexport type LogCell = string | number | boolean | null;\nexport interface LogQueryResult {\n error?: string;\n columns: string[];\n rows: LogCell[][];\n truncated: boolean;\n elapsedMs: number;\n}\nexport interface LogPartition {\n clusterId: string;\n contextId: string;\n}\nexport type LogRequest = LogPartition &\n (\n | { operation: \"permit\"; lane: \"start\" | \"bytes\"; bytes?: number }\n | { operation: \"stream-open\"; streamId: string; follow: boolean }\n | { operation: \"stream-close\" | \"stream-renew\"; streamId: string }\n | { operation: \"create\"; target: LogTarget }\n | { operation: \"append\"; captureId: string; records: LogRecord[] }\n | { operation: \"finish\"; captureId: string; partial: boolean; notes: string[] }\n | { operation: \"list\" }\n | { operation: \"clear-persistent\" }\n | { operation: \"delete\"; captureId: string }\n | { operation: \"set-persistent\"; captureId: string; persistent: boolean }\n | { operation: \"save-sql\"; captureId: string; sql: string }\n | { operation: \"query\"; captureId: string; queryId: string; sql: string }\n | { operation: \"cancel\"; queryId: string }\n | { operation: \"export\"; captureId: string }\n );\n"],"mappings":";AAAA,IAAa,qBAAqB;AAClC,IAAa,4BAA4B;CACvC,UAAU;CACV,WAAW,KAAK,OAAO;CACvB,WAAW;CACX,YAAY,IAAI,OAAO;CACvB,aAAa,KAAK;CAClB,cAAc,MAAM,OAAO;CAC3B,aAAa;CACb,UAAU;CACV,SAAS;CACT,YAAY;CACZ,aAAa,IAAI,OAAO;CACxB,SAAS;CACT,OAAO,OAAU;CACjB,cAAc;CACd,cAAc,OAAO;CACrB,oBAAoB;CACpB,oBAAoB,IAAI,OAAO;CAC/B,mBAAmB;CACnB,gBAAgB,OAAO;CACvB,oBAAoB;CACpB,iBAAiB;AACnB;AAIA,IAAa,oBAAqC,EAAE,GAAG,0BAA0B"}
|
|
@@ -5,7 +5,7 @@ let node_fs_promises = require("node:fs/promises");
|
|
|
5
5
|
//#region src/main/power-logs-child.ts
|
|
6
6
|
var port = process.parentPort;
|
|
7
7
|
if (!port) throw new Error("Log process requires parent");
|
|
8
|
-
var engine = new require_power_logs_database.PowerLogsDatabase(process.argv[2]);
|
|
8
|
+
var engine = new require_power_logs_database.PowerLogsDatabase(process.argv[2], process.argv[3]);
|
|
9
9
|
var queries = /* @__PURE__ */ new Map();
|
|
10
10
|
var queryKey = (request) => JSON.stringify([
|
|
11
11
|
request.clusterId,
|
|
@@ -69,10 +69,10 @@ port.on("message", ({ data }) => {
|
|
|
69
69
|
id: data.id,
|
|
70
70
|
result
|
|
71
71
|
});
|
|
72
|
-
} catch {
|
|
72
|
+
} catch (error) {
|
|
73
73
|
port.postMessage({
|
|
74
74
|
id: data.id,
|
|
75
|
-
error: "Log operation failed or exceeded its limit"
|
|
75
|
+
error: error instanceof Error && error.message === "Saved snapshot limit reached. Delete a saved snapshot before keeping another." ? error.message : "Log operation failed or exceeded its limit"
|
|
76
76
|
});
|
|
77
77
|
} finally {
|
|
78
78
|
queuedCount--;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-logs-child.js","names":[],"sources":["../../src/main/power-logs-child.ts"],"sourcesContent":["import { copyFile } from \"node:fs/promises\";\nimport { applyPowerLogsLimits } from \"../common/power-logs-limits\";\nimport { PowerLogsDatabase } from \"./power-logs-database\";\n\nimport type { LogRequest } from \"../common/power-logs\";\n\nconst port = process.parentPort;\nif (!port) throw new Error(\"Log process requires parent\");\nconst engine = new PowerLogsDatabase(process.argv[2]);\nconst queries = new Map<string, { id: number; started: boolean; canceled: boolean }>();\nconst queryKey = (request: LogRequest & { queryId: string }) =>\n JSON.stringify([request.clusterId, request.contextId, request.queryId]);\nlet queue = Promise.resolve(),\n queuedCount = 0;\nport.on(\n \"message\",\n ({ data }: { data: { id: number; request: LogRequest; destination?: string; limits?: unknown } }) => {\n if (data.request.operation === \"cancel\") {\n const pending = queries.get(queryKey(data.request));\n if (pending && !pending.started) {\n if (!pending.canceled) port.postMessage({ id: pending.id, error: \"Log query canceled\" });\n pending.canceled = true;\n port.postMessage({ id: data.id, result: true });\n return;\n }\n void engine.run(data.request).then(\n (result) => port.postMessage({ id: data.id, result }),\n () => port.postMessage({ id: data.id, error: \"Log operation failed or exceeded its limit\" }),\n );\n return;\n }\n const key = data.request.operation === \"query\" ? queryKey(data.request) : undefined;\n if (key && queries.size) {\n port.postMessage({ id: data.id, error: \"Log query is busy\" });\n return;\n }\n if (queuedCount >= 16) {\n port.postMessage({ id: data.id, error: \"Log operation queue is full\" });\n return;\n }\n const pending = key ? { id: data.id, started: false, canceled: false } : undefined;\n if (key && pending) queries.set(key, pending);\n queuedCount++;\n const run = async () => {\n try {\n if (pending?.canceled) return;\n applyPowerLogsLimits(data.limits);\n if (pending) pending.started = true;\n const result =\n data.request.operation === \"export\" && data.destination\n ? await copyFile(engine.exportPath(data.request), data.destination).then(() => true)\n : await engine.run(data.request);\n port.postMessage({ id: data.id, result });\n } catch {\n port.postMessage({
|
|
1
|
+
{"version":3,"file":"power-logs-child.js","names":[],"sources":["../../src/main/power-logs-child.ts"],"sourcesContent":["import { copyFile } from \"node:fs/promises\";\nimport { applyPowerLogsLimits } from \"../common/power-logs-limits\";\nimport { PowerLogsDatabase } from \"./power-logs-database\";\n\nimport type { LogRequest } from \"../common/power-logs\";\n\nconst port = process.parentPort;\nif (!port) throw new Error(\"Log process requires parent\");\nconst engine = new PowerLogsDatabase(process.argv[2], process.argv[3]);\nconst queries = new Map<string, { id: number; started: boolean; canceled: boolean }>();\nconst queryKey = (request: LogRequest & { queryId: string }) =>\n JSON.stringify([request.clusterId, request.contextId, request.queryId]);\nlet queue = Promise.resolve(),\n queuedCount = 0;\nport.on(\n \"message\",\n ({ data }: { data: { id: number; request: LogRequest; destination?: string; limits?: unknown } }) => {\n if (data.request.operation === \"cancel\") {\n const pending = queries.get(queryKey(data.request));\n if (pending && !pending.started) {\n if (!pending.canceled) port.postMessage({ id: pending.id, error: \"Log query canceled\" });\n pending.canceled = true;\n port.postMessage({ id: data.id, result: true });\n return;\n }\n void engine.run(data.request).then(\n (result) => port.postMessage({ id: data.id, result }),\n () => port.postMessage({ id: data.id, error: \"Log operation failed or exceeded its limit\" }),\n );\n return;\n }\n const key = data.request.operation === \"query\" ? queryKey(data.request) : undefined;\n if (key && queries.size) {\n port.postMessage({ id: data.id, error: \"Log query is busy\" });\n return;\n }\n if (queuedCount >= 16) {\n port.postMessage({ id: data.id, error: \"Log operation queue is full\" });\n return;\n }\n const pending = key ? { id: data.id, started: false, canceled: false } : undefined;\n if (key && pending) queries.set(key, pending);\n queuedCount++;\n const run = async () => {\n try {\n if (pending?.canceled) return;\n applyPowerLogsLimits(data.limits);\n if (pending) pending.started = true;\n const result =\n data.request.operation === \"export\" && data.destination\n ? await copyFile(engine.exportPath(data.request), data.destination).then(() => true)\n : await engine.run(data.request);\n port.postMessage({ id: data.id, result });\n } catch (error) {\n port.postMessage({\n id: data.id,\n error:\n error instanceof Error &&\n error.message === \"Saved snapshot limit reached. Delete a saved snapshot before keeping another.\"\n ? error.message\n : \"Log operation failed or exceeded its limit\",\n });\n } finally {\n queuedCount--;\n if (key) queries.delete(key);\n }\n };\n queue = queue.then(run);\n },\n);\n"],"mappings":";;;;;AAMA,IAAM,OAAO,QAAQ;AACrB,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,6BAA6B;AACxD,IAAM,SAAS,IAAI,4BAAA,kBAAkB,QAAQ,KAAK,IAAI,QAAQ,KAAK,EAAE;AACrE,IAAM,0BAAU,IAAI,IAAiE;AACrF,IAAM,YAAY,YAChB,KAAK,UAAU;CAAC,QAAQ;CAAW,QAAQ;CAAW,QAAQ;AAAO,CAAC;AACxE,IAAI,QAAQ,QAAQ,QAAQ,GAC1B,cAAc;AAChB,KAAK,GACH,YACC,EAAE,WAAkG;CACnG,IAAI,KAAK,QAAQ,cAAc,UAAU;EACvC,MAAM,UAAU,QAAQ,IAAI,SAAS,KAAK,OAAO,CAAC;EAClD,IAAI,WAAW,CAAC,QAAQ,SAAS;GAC/B,IAAI,CAAC,QAAQ,UAAU,KAAK,YAAY;IAAE,IAAI,QAAQ;IAAI,OAAO;GAAqB,CAAC;GACvF,QAAQ,WAAW;GACnB,KAAK,YAAY;IAAE,IAAI,KAAK;IAAI,QAAQ;GAAK,CAAC;GAC9C;EACF;EACA,OAAY,IAAI,KAAK,OAAO,CAAC,CAAC,MAC3B,WAAW,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI;EAAO,CAAC,SAC9C,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI,OAAO;EAA6C,CAAC,CAC7F;EACA;CACF;CACA,MAAM,MAAM,KAAK,QAAQ,cAAc,UAAU,SAAS,KAAK,OAAO,IAAI,KAAA;CAC1E,IAAI,OAAO,QAAQ,MAAM;EACvB,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI,OAAO;EAAoB,CAAC;EAC5D;CACF;CACA,IAAI,eAAe,IAAI;EACrB,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI,OAAO;EAA8B,CAAC;EACtE;CACF;CACA,MAAM,UAAU,MAAM;EAAE,IAAI,KAAK;EAAI,SAAS;EAAO,UAAU;CAAM,IAAI,KAAA;CACzE,IAAI,OAAO,SAAS,QAAQ,IAAI,KAAK,OAAO;CAC5C;CACA,MAAM,MAAM,YAAY;EACtB,IAAI;GACF,IAAI,SAAS,UAAU;GACvB,0BAAA,qBAAqB,KAAK,MAAM;GAChC,IAAI,SAAS,QAAQ,UAAU;GAC/B,MAAM,SACJ,KAAK,QAAQ,cAAc,YAAY,KAAK,cACxC,OAAA,GAAA,iBAAA,SAAA,CAAe,OAAO,WAAW,KAAK,OAAO,GAAG,KAAK,WAAW,CAAC,CAAC,WAAW,IAAI,IACjF,MAAM,OAAO,IAAI,KAAK,OAAO;GACnC,KAAK,YAAY;IAAE,IAAI,KAAK;IAAI;GAAO,CAAC;EAC1C,SAAS,OAAO;GACd,KAAK,YAAY;IACf,IAAI,KAAK;IACT,OACE,iBAAiB,SACjB,MAAM,YAAY,kFACd,MAAM,UACN;GACR,CAAC;EACH,UAAU;GACR;GACA,IAAI,KAAK,QAAQ,OAAO,GAAG;EAC7B;CACF;CACA,QAAQ,MAAM,KAAK,GAAG;AACxB,CACF"}
|
|
@@ -7,16 +7,62 @@ let node_readline = require("node:readline");
|
|
|
7
7
|
//#region src/main/power-logs-database.ts
|
|
8
8
|
var PowerLogsDatabase = class {
|
|
9
9
|
directory;
|
|
10
|
+
persistentDirectory;
|
|
10
11
|
captures = /* @__PURE__ */ new Map();
|
|
11
12
|
closed = false;
|
|
12
13
|
timer;
|
|
13
|
-
constructor(directory) {
|
|
14
|
+
constructor(directory, persistentDirectory) {
|
|
14
15
|
this.directory = directory;
|
|
16
|
+
this.persistentDirectory = persistentDirectory;
|
|
15
17
|
(0, node_fs.mkdirSync)(directory, {
|
|
16
18
|
recursive: true,
|
|
17
19
|
mode: 448
|
|
18
20
|
});
|
|
19
21
|
(0, node_fs.chmodSync)(directory, 448);
|
|
22
|
+
if (persistentDirectory) {
|
|
23
|
+
(0, node_fs.mkdirSync)(persistentDirectory, {
|
|
24
|
+
recursive: true,
|
|
25
|
+
mode: 448
|
|
26
|
+
});
|
|
27
|
+
(0, node_fs.chmodSync)(persistentDirectory, 448);
|
|
28
|
+
for (const id of (0, node_fs.readdirSync)(persistentDirectory)) {
|
|
29
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.pending$/.test(id)) {
|
|
30
|
+
const pending = (0, node_path.join)(persistentDirectory, id);
|
|
31
|
+
const info = (0, node_fs.lstatSync)(pending);
|
|
32
|
+
if (info.isDirectory() && !info.isSymbolicLink() && (!process.getuid || info.uid === process.getuid())) (0, node_fs.rmSync)(pending, {
|
|
33
|
+
recursive: true,
|
|
34
|
+
force: true
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(id)) continue;
|
|
39
|
+
const directory = (0, node_path.join)(persistentDirectory, id);
|
|
40
|
+
try {
|
|
41
|
+
if (!(0, node_fs.lstatSync)(directory).isDirectory() || (0, node_fs.lstatSync)(directory).isSymbolicLink()) continue;
|
|
42
|
+
const manifest = (0, node_path.join)(directory, "capture.json");
|
|
43
|
+
if (!(0, node_fs.lstatSync)(manifest).isFile() || (0, node_fs.lstatSync)(manifest).isSymbolicLink()) continue;
|
|
44
|
+
const saved = JSON.parse((0, node_fs.readFileSync)(manifest, "utf8"));
|
|
45
|
+
if (saved.meta?.id === id && saved.meta.persistent === false) {
|
|
46
|
+
(0, node_fs.rmSync)(directory, {
|
|
47
|
+
recursive: true,
|
|
48
|
+
force: true
|
|
49
|
+
});
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (saved.meta?.id !== id || saved.meta.persistent !== true || !["ready", "partial"].includes(saved.meta.state) || typeof saved.partition !== "string") continue;
|
|
53
|
+
const spool = (0, node_path.join)(directory, "logs.ndjson"), database = (0, node_path.join)(directory, "logs.sqlite");
|
|
54
|
+
if (![spool, database].every((path) => (0, node_fs.lstatSync)(path).isFile() && !(0, node_fs.lstatSync)(path).isSymbolicLink())) continue;
|
|
55
|
+
this.captures.set(id, {
|
|
56
|
+
meta: saved.meta,
|
|
57
|
+
partition: saved.partition,
|
|
58
|
+
limits: saved.limits,
|
|
59
|
+
directory,
|
|
60
|
+
spool,
|
|
61
|
+
database
|
|
62
|
+
});
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
20
66
|
this.timer = setInterval(() => this.expire(), 3e4);
|
|
21
67
|
this.timer.unref();
|
|
22
68
|
}
|
|
@@ -25,7 +71,7 @@ var PowerLogsDatabase = class {
|
|
|
25
71
|
}
|
|
26
72
|
expire() {
|
|
27
73
|
for (const [id, capture] of this.captures) {
|
|
28
|
-
if (capture.meta.expiresAt > Date.now()) continue;
|
|
74
|
+
if (capture.meta.persistent || capture.meta.expiresAt > Date.now()) continue;
|
|
29
75
|
try {
|
|
30
76
|
this.remove(id);
|
|
31
77
|
} catch {}
|
|
@@ -35,6 +81,10 @@ var PowerLogsDatabase = class {
|
|
|
35
81
|
const capture = this.captures.get(id);
|
|
36
82
|
if (!capture) return;
|
|
37
83
|
try {
|
|
84
|
+
if (capture.retiredDirectory) (0, node_fs.rmSync)(capture.retiredDirectory, {
|
|
85
|
+
recursive: true,
|
|
86
|
+
force: true
|
|
87
|
+
});
|
|
38
88
|
(0, node_fs.rmSync)(capture.directory, {
|
|
39
89
|
recursive: true,
|
|
40
90
|
force: true
|
|
@@ -49,7 +99,7 @@ var PowerLogsDatabase = class {
|
|
|
49
99
|
}
|
|
50
100
|
capture(request) {
|
|
51
101
|
const capture = this.captures.get(request.captureId);
|
|
52
|
-
if (!capture || capture.partition !== this.partition(request) || capture.meta.expiresAt <= Date.now()) throw new Error("Capture unavailable");
|
|
102
|
+
if (!capture || capture.partition !== this.partition(request) || !capture.meta.persistent && capture.meta.expiresAt <= Date.now()) throw new Error("Capture unavailable");
|
|
53
103
|
return capture;
|
|
54
104
|
}
|
|
55
105
|
async run(request) {
|
|
@@ -58,9 +108,14 @@ var PowerLogsDatabase = class {
|
|
|
58
108
|
if (request.operation === "permit" || "streamId" in request) throw new Error("Log permits require main process");
|
|
59
109
|
const partition = this.partition(request);
|
|
60
110
|
if (request.operation === "cancel") return true;
|
|
61
|
-
if (request.operation === "
|
|
111
|
+
if (request.operation === "clear-persistent") {
|
|
112
|
+
for (const [id, capture] of this.captures) if (capture.meta.persistent) this.remove(id);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (request.operation === "list") return [...this.captures.values()].filter((c) => c.partition === partition && (c.meta.persistent || c.meta.expiresAt > Date.now())).map((c) => ({ ...c.meta }));
|
|
62
116
|
if (request.operation === "create") {
|
|
63
|
-
|
|
117
|
+
const captures = [...this.captures.values()];
|
|
118
|
+
if (captures.filter((capture) => capture.partition === partition).length >= require_power_logs.POWER_LOGS_LIMITS.captures || captures.filter((capture) => !capture.meta.persistent).length >= require_power_logs.POWER_LOGS_LIMITS.captures) throw new Error("Capture limit reached");
|
|
64
119
|
const id = (0, node_crypto.randomUUID)(), directory = (0, node_path.join)(this.directory, id), now = Date.now();
|
|
65
120
|
(0, node_fs.mkdirSync)(directory, { mode: 448 });
|
|
66
121
|
const spool = (0, node_path.join)(directory, "logs.ndjson");
|
|
@@ -93,9 +148,102 @@ var PowerLogsDatabase = class {
|
|
|
93
148
|
this.remove(request.captureId);
|
|
94
149
|
return true;
|
|
95
150
|
}
|
|
151
|
+
if (request.operation === "set-persistent") {
|
|
152
|
+
if (typeof request.persistent !== "boolean") throw new Error("Invalid persistence flag");
|
|
153
|
+
if (capture.meta.state === "collecting") throw new Error("Finish capture before saving");
|
|
154
|
+
if (request.persistent === !!capture.meta.persistent) return { ...capture.meta };
|
|
155
|
+
if (request.persistent) {
|
|
156
|
+
if ([...this.captures.values()].filter((item) => item.meta.persistent).length >= require_power_logs.POWER_LOGS_LIMITS.captures) throw new Error("Saved snapshot limit reached. Delete a saved snapshot before keeping another.");
|
|
157
|
+
if (!this.persistentDirectory) throw new Error("Persistent storage unavailable");
|
|
158
|
+
const directory = (0, node_path.join)(this.persistentDirectory, capture.meta.id);
|
|
159
|
+
if (capture.directory === directory) {
|
|
160
|
+
this.saveMetadata({
|
|
161
|
+
...capture,
|
|
162
|
+
meta: {
|
|
163
|
+
...capture.meta,
|
|
164
|
+
persistent: true
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
capture.meta.persistent = true;
|
|
168
|
+
return { ...capture.meta };
|
|
169
|
+
}
|
|
170
|
+
const staging = (0, node_path.join)(this.persistentDirectory, `${capture.meta.id}.pending`);
|
|
171
|
+
try {
|
|
172
|
+
(0, node_fs.cpSync)(capture.directory, staging, {
|
|
173
|
+
recursive: true,
|
|
174
|
+
errorOnExist: true,
|
|
175
|
+
force: false
|
|
176
|
+
});
|
|
177
|
+
this.saveMetadata({
|
|
178
|
+
...capture,
|
|
179
|
+
directory,
|
|
180
|
+
meta: {
|
|
181
|
+
...capture.meta,
|
|
182
|
+
persistent: true
|
|
183
|
+
}
|
|
184
|
+
}, staging);
|
|
185
|
+
(0, node_fs.renameSync)(staging, directory);
|
|
186
|
+
} catch {
|
|
187
|
+
(0, node_fs.rmSync)(staging, {
|
|
188
|
+
recursive: true,
|
|
189
|
+
force: true
|
|
190
|
+
});
|
|
191
|
+
throw new Error("Persistent storage unavailable");
|
|
192
|
+
}
|
|
193
|
+
capture.retiredDirectory = capture.directory;
|
|
194
|
+
capture.directory = directory;
|
|
195
|
+
capture.spool = (0, node_path.join)(directory, "logs.ndjson");
|
|
196
|
+
capture.database = (0, node_path.join)(directory, "logs.sqlite");
|
|
197
|
+
capture.meta.persistent = true;
|
|
198
|
+
} else {
|
|
199
|
+
if ([...this.captures.values()].filter((item) => !item.meta.persistent).length >= require_power_logs.POWER_LOGS_LIMITS.captures) throw new Error("Capture limit reached");
|
|
200
|
+
const directory = (0, node_path.join)(this.directory, capture.meta.id);
|
|
201
|
+
const staging = `${directory}.pending`;
|
|
202
|
+
try {
|
|
203
|
+
(0, node_fs.cpSync)(capture.directory, staging, {
|
|
204
|
+
recursive: true,
|
|
205
|
+
errorOnExist: true,
|
|
206
|
+
force: false
|
|
207
|
+
});
|
|
208
|
+
(0, node_fs.rmSync)(directory, {
|
|
209
|
+
recursive: true,
|
|
210
|
+
force: true
|
|
211
|
+
});
|
|
212
|
+
(0, node_fs.renameSync)(staging, directory);
|
|
213
|
+
(0, node_fs.rmSync)(capture.directory, {
|
|
214
|
+
recursive: true,
|
|
215
|
+
force: true
|
|
216
|
+
});
|
|
217
|
+
} catch {
|
|
218
|
+
(0, node_fs.rmSync)(staging, {
|
|
219
|
+
recursive: true,
|
|
220
|
+
force: true
|
|
221
|
+
});
|
|
222
|
+
throw new Error("Temporary storage unavailable");
|
|
223
|
+
}
|
|
224
|
+
capture.directory = directory;
|
|
225
|
+
capture.retiredDirectory = void 0;
|
|
226
|
+
capture.spool = (0, node_path.join)(directory, "logs.ndjson");
|
|
227
|
+
capture.database = (0, node_path.join)(directory, "logs.sqlite");
|
|
228
|
+
capture.meta = {
|
|
229
|
+
...capture.meta,
|
|
230
|
+
persistent: false,
|
|
231
|
+
expiresAt: Date.now() + require_power_logs.POWER_LOGS_LIMITS.ttlMs
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { ...capture.meta };
|
|
235
|
+
}
|
|
96
236
|
if (request.operation === "save-sql") {
|
|
97
237
|
if (typeof request.sql !== "string" || request.sql.length > 65536 || request.sql.includes("\0")) throw new Error("Invalid SQL draft");
|
|
98
|
-
|
|
238
|
+
const meta = {
|
|
239
|
+
...capture.meta,
|
|
240
|
+
lastSql: request.sql
|
|
241
|
+
};
|
|
242
|
+
if (capture.meta.persistent) this.saveMetadata({
|
|
243
|
+
...capture,
|
|
244
|
+
meta
|
|
245
|
+
});
|
|
246
|
+
capture.meta = meta;
|
|
99
247
|
return { ...capture.meta };
|
|
100
248
|
}
|
|
101
249
|
if (request.operation === "export") throw new Error("Export requires trusted destination");
|
|
@@ -165,12 +313,22 @@ var PowerLogsDatabase = class {
|
|
|
165
313
|
return {
|
|
166
314
|
database: capture.database,
|
|
167
315
|
captureId: capture.meta.id,
|
|
168
|
-
expiresAt: capture.meta.expiresAt
|
|
316
|
+
expiresAt: capture.meta.persistent ? Number.MAX_SAFE_INTEGER : capture.meta.expiresAt
|
|
169
317
|
};
|
|
170
318
|
}
|
|
319
|
+
saveMetadata(capture, directory = capture.directory) {
|
|
320
|
+
const temporary = (0, node_path.join)(directory, "capture.json.pending");
|
|
321
|
+
(0, node_fs.writeFileSync)(temporary, JSON.stringify({
|
|
322
|
+
meta: capture.meta,
|
|
323
|
+
partition: capture.partition,
|
|
324
|
+
limits: capture.limits
|
|
325
|
+
}), { mode: 384 });
|
|
326
|
+
(0, node_fs.renameSync)(temporary, (0, node_path.join)(directory, "capture.json"));
|
|
327
|
+
}
|
|
171
328
|
close() {
|
|
172
329
|
this.closed = true;
|
|
173
330
|
clearInterval(this.timer);
|
|
331
|
+
for (const [id, capture] of this.captures) if (!capture.meta.persistent) this.remove(id);
|
|
174
332
|
this.captures.clear();
|
|
175
333
|
(0, node_fs.rmSync)(this.directory, {
|
|
176
334
|
recursive: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-logs-database.js","names":[],"sources":["../../src/main/power-logs-database.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { appendFileSync, chmodSync, createReadStream, mkdirSync, rmSync, truncateSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { POWER_LOGS_LIMITS as L, type LogCapture, type LogRequest } from \"../common/power-logs\";\n\nimport type { PowerLogsQuerySnapshot } from \"./power-logs-query\";\n\ntype Capture = {\n meta: LogCapture;\n partition: string;\n directory: string;\n spool: string;\n database: string;\n limits: { captureBytes: number; captureRows: number };\n};\nexport class PowerLogsDatabase {\n private captures = new Map<string, Capture>();\n private closed = false;\n private timer: ReturnType<typeof setInterval>;\n constructor(private directory: string) {\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n chmodSync(directory, 0o700);\n this.timer = setInterval(() => this.expire(), 30000);\n this.timer.unref();\n }\n private partition(request: LogRequest): string {\n return JSON.stringify([request.clusterId, request.contextId]);\n }\n private expire(): void {\n for (const [id, capture] of this.captures) {\n if (capture.meta.expiresAt > Date.now()) continue;\n try {\n this.remove(id);\n } catch {\n // retain expired entries within the capture limit until a later sweep can remove locked files\n }\n }\n }\n private remove(id: string): void {\n const capture = this.captures.get(id);\n if (!capture) return;\n try {\n rmSync(capture.directory, { recursive: true, force: true });\n } catch {\n throw new Error(\"Log capture storage is busy or unavailable\");\n }\n this.captures.delete(id);\n }\n exportPath(request: LogRequest & { captureId: string }): string {\n return this.capture(request).spool;\n }\n private capture(request: LogRequest & { captureId: string }): Capture {\n const capture = this.captures.get(request.captureId);\n if (!capture || capture.partition !== this.partition(request) || capture.meta.expiresAt <= Date.now())\n throw new Error(\"Capture unavailable\");\n return capture;\n }\n async run(request: LogRequest): Promise<unknown> {\n if (this.closed) throw new Error(\"Log storage unavailable\");\n this.expire();\n if (request.operation === \"permit\" || \"streamId\" in request) throw new Error(\"Log permits require main process\");\n const partition = this.partition(request);\n if (request.operation === \"cancel\") return true;\n if (request.operation === \"list\")\n return [...this.captures.values()]\n .filter((c) => c.partition === partition && c.meta.expiresAt > Date.now())\n .map((c) => ({ ...c.meta }));\n if (request.operation === \"create\") {\n if (this.captures.size >= L.captures) throw new Error(\"Capture limit reached\");\n const id = randomUUID(),\n directory = join(this.directory, id),\n now = Date.now();\n mkdirSync(directory, { mode: 0o700 });\n const spool = join(directory, \"logs.ndjson\");\n writeFileSync(spool, \"\", { mode: 0o600 });\n const meta: LogCapture = {\n id,\n target: request.target,\n createdAt: now,\n expiresAt: now + L.ttlMs,\n state: \"collecting\",\n rows: 0,\n bytes: 0,\n notes: [],\n };\n this.captures.set(id, {\n meta,\n partition,\n directory,\n spool,\n database: join(directory, \"logs.sqlite\"),\n limits: { captureBytes: L.captureBytes, captureRows: L.captureRows },\n });\n return { ...meta };\n }\n const capture = this.capture(request);\n if (request.operation === \"delete\") {\n this.remove(request.captureId);\n return true;\n }\n if (request.operation === \"save-sql\") {\n if (typeof request.sql !== \"string\" || request.sql.length > 65536 || request.sql.includes(\"\\0\"))\n throw new Error(\"Invalid SQL draft\");\n capture.meta.lastSql = request.sql;\n return { ...capture.meta };\n }\n if (request.operation === \"export\") throw new Error(\"Export requires trusted destination\");\n if (request.operation === \"append\") {\n if (capture.meta.state !== \"collecting\") throw new Error(\"Capture is complete\");\n if (request.records.length > L.batchRows) throw new Error(\"Log batch exceeds limit\");\n const data = request.records\n .map((record) => JSON.stringify({ ...record, previous: record.previous === true }) + \"\\n\")\n .join(\"\");\n const bytes = Buffer.byteLength(data);\n if (\n bytes > L.batchBytes ||\n capture.meta.bytes + bytes > capture.limits.captureBytes ||\n capture.meta.rows + request.records.length > capture.limits.captureRows\n )\n throw new Error(\"Capture limit reached\");\n try {\n appendFileSync(capture.spool, data);\n } catch {\n truncateSync(capture.spool, capture.meta.bytes);\n throw new Error(\"Log capture storage unavailable\");\n }\n capture.meta.rows += request.records.length;\n capture.meta.bytes += bytes;\n return { ...capture.meta };\n }\n if (request.operation === \"finish\") {\n if (capture.meta.state !== \"collecting\") return { ...capture.meta };\n let database: DatabaseSync | undefined;\n try {\n writeFileSync(capture.database, \"\", { mode: 0o600 });\n database = new DatabaseSync(capture.database, { allowExtension: false });\n database.exec(\n \"PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA cache_size=-16384; PRAGMA temp_store=MEMORY\",\n );\n database.exec(\n \"CREATE TABLE logs(timestamp INTEGER, pod TEXT, podUid TEXT, container TEXT, level TEXT, message TEXT, fieldsJson TEXT, previous INTEGER CHECK(previous IN (0,1)))\",\n );\n const insert = database.prepare(\"INSERT INTO logs VALUES (?, ?, ?, ?, ?, ?, ?, ?)\");\n const input = createReadStream(capture.spool);\n const lines = createInterface({ input, crlfDelay: Infinity });\n let count = 0;\n database.exec(\"BEGIN\");\n try {\n for await (const line of lines) {\n const record = JSON.parse(line);\n insert.run(\n record.timestamp,\n record.pod,\n record.podUid,\n record.container,\n record.level,\n record.message,\n record.fieldsJson,\n record.previous ? 1 : 0,\n );\n if (++count % 5000 === 0) {\n database.exec(\"COMMIT; BEGIN\");\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n database.exec(\"COMMIT\");\n } finally {\n lines.close();\n input.destroy();\n }\n database.exec(\"CREATE INDEX logs_timestamp ON logs(timestamp)\");\n } catch {\n database?.close();\n database = undefined;\n rmSync(capture.database, { force: true });\n throw new Error(\"Log capture storage unavailable\");\n } finally {\n database?.close();\n }\n capture.meta.state = request.partial ? \"partial\" : \"ready\";\n capture.meta.notes = request.notes;\n return { ...capture.meta };\n }\n if (capture.meta.state === \"collecting\") throw new Error(\"Finish capture before querying\");\n return {\n database: capture.database,\n captureId: capture.meta.id,\n expiresAt: capture.meta.expiresAt,\n } satisfies PowerLogsQuerySnapshot;\n }\n close(): void {\n this.closed = true;\n clearInterval(this.timer);\n this.captures.clear();\n rmSync(this.directory, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"power-logs-database.js","names":[],"sources":["../../src/main/power-logs-database.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n appendFileSync,\n chmodSync,\n cpSync,\n createReadStream,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmSync,\n truncateSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { POWER_LOGS_LIMITS as L, type LogCapture, type LogRequest } from \"../common/power-logs\";\n\nimport type { PowerLogsQuerySnapshot } from \"./power-logs-query\";\n\ntype Capture = {\n meta: LogCapture;\n partition: string;\n directory: string;\n spool: string;\n database: string;\n retiredDirectory?: string;\n limits: { captureBytes: number; captureRows: number };\n};\nexport class PowerLogsDatabase {\n private captures = new Map<string, Capture>();\n private closed = false;\n private timer: ReturnType<typeof setInterval>;\n constructor(\n private directory: string,\n private persistentDirectory?: string,\n ) {\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n chmodSync(directory, 0o700);\n if (persistentDirectory) {\n mkdirSync(persistentDirectory, { recursive: true, mode: 0o700 });\n chmodSync(persistentDirectory, 0o700);\n for (const id of readdirSync(persistentDirectory)) {\n if (/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\\.pending$/.test(id)) {\n const pending = join(persistentDirectory, id);\n const info = lstatSync(pending);\n if (info.isDirectory() && !info.isSymbolicLink() && (!process.getuid || info.uid === process.getuid())) {\n rmSync(pending, { recursive: true, force: true });\n }\n continue;\n }\n if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(id)) continue;\n const directory = join(persistentDirectory, id);\n try {\n if (!lstatSync(directory).isDirectory() || lstatSync(directory).isSymbolicLink()) continue;\n const manifest = join(directory, \"capture.json\");\n if (!lstatSync(manifest).isFile() || lstatSync(manifest).isSymbolicLink()) continue;\n const saved = JSON.parse(readFileSync(manifest, \"utf8\"));\n if (saved.meta?.id === id && saved.meta.persistent === false) {\n rmSync(directory, { recursive: true, force: true });\n continue;\n }\n if (\n saved.meta?.id !== id ||\n saved.meta.persistent !== true ||\n ![\"ready\", \"partial\"].includes(saved.meta.state) ||\n typeof saved.partition !== \"string\"\n )\n continue;\n const spool = join(directory, \"logs.ndjson\"),\n database = join(directory, \"logs.sqlite\");\n if (![spool, database].every((path) => lstatSync(path).isFile() && !lstatSync(path).isSymbolicLink()))\n continue;\n this.captures.set(id, {\n meta: saved.meta,\n partition: saved.partition,\n limits: saved.limits,\n directory,\n spool,\n database,\n });\n } catch {\n // leave unreadable saved captures available for recovery\n }\n }\n }\n this.timer = setInterval(() => this.expire(), 30000);\n this.timer.unref();\n }\n private partition(request: LogRequest): string {\n return JSON.stringify([request.clusterId, request.contextId]);\n }\n private expire(): void {\n for (const [id, capture] of this.captures) {\n if (capture.meta.persistent || capture.meta.expiresAt > Date.now()) continue;\n try {\n this.remove(id);\n } catch {\n // retain expired entries within the capture limit until a later sweep can remove locked files\n }\n }\n }\n private remove(id: string): void {\n const capture = this.captures.get(id);\n if (!capture) return;\n try {\n if (capture.retiredDirectory) rmSync(capture.retiredDirectory, { recursive: true, force: true });\n rmSync(capture.directory, { recursive: true, force: true });\n } catch {\n throw new Error(\"Log capture storage is busy or unavailable\");\n }\n this.captures.delete(id);\n }\n exportPath(request: LogRequest & { captureId: string }): string {\n return this.capture(request).spool;\n }\n private capture(request: LogRequest & { captureId: string }): Capture {\n const capture = this.captures.get(request.captureId);\n if (\n !capture ||\n capture.partition !== this.partition(request) ||\n (!capture.meta.persistent && capture.meta.expiresAt <= Date.now())\n )\n throw new Error(\"Capture unavailable\");\n return capture;\n }\n async run(request: LogRequest): Promise<unknown> {\n if (this.closed) throw new Error(\"Log storage unavailable\");\n this.expire();\n if (request.operation === \"permit\" || \"streamId\" in request) throw new Error(\"Log permits require main process\");\n const partition = this.partition(request);\n if (request.operation === \"cancel\") return true;\n if (request.operation === \"clear-persistent\") {\n for (const [id, capture] of this.captures) if (capture.meta.persistent) this.remove(id);\n return true;\n }\n if (request.operation === \"list\")\n return [...this.captures.values()]\n .filter((c) => c.partition === partition && (c.meta.persistent || c.meta.expiresAt > Date.now()))\n .map((c) => ({ ...c.meta }));\n if (request.operation === \"create\") {\n const captures = [...this.captures.values()];\n if (\n captures.filter((capture) => capture.partition === partition).length >= L.captures ||\n captures.filter((capture) => !capture.meta.persistent).length >= L.captures\n )\n throw new Error(\"Capture limit reached\");\n const id = randomUUID(),\n directory = join(this.directory, id),\n now = Date.now();\n mkdirSync(directory, { mode: 0o700 });\n const spool = join(directory, \"logs.ndjson\");\n writeFileSync(spool, \"\", { mode: 0o600 });\n const meta: LogCapture = {\n id,\n target: request.target,\n createdAt: now,\n expiresAt: now + L.ttlMs,\n state: \"collecting\",\n rows: 0,\n bytes: 0,\n notes: [],\n };\n this.captures.set(id, {\n meta,\n partition,\n directory,\n spool,\n database: join(directory, \"logs.sqlite\"),\n limits: { captureBytes: L.captureBytes, captureRows: L.captureRows },\n });\n return { ...meta };\n }\n const capture = this.capture(request);\n if (request.operation === \"delete\") {\n this.remove(request.captureId);\n return true;\n }\n if (request.operation === \"set-persistent\") {\n if (typeof request.persistent !== \"boolean\") throw new Error(\"Invalid persistence flag\");\n if (capture.meta.state === \"collecting\") throw new Error(\"Finish capture before saving\");\n if (request.persistent === !!capture.meta.persistent) return { ...capture.meta };\n if (request.persistent) {\n if ([...this.captures.values()].filter((item) => item.meta.persistent).length >= L.captures)\n throw new Error(\"Saved snapshot limit reached. Delete a saved snapshot before keeping another.\");\n if (!this.persistentDirectory) throw new Error(\"Persistent storage unavailable\");\n const directory = join(this.persistentDirectory, capture.meta.id);\n if (capture.directory === directory) {\n this.saveMetadata({ ...capture, meta: { ...capture.meta, persistent: true } });\n capture.meta.persistent = true;\n return { ...capture.meta };\n }\n const staging = join(this.persistentDirectory, `${capture.meta.id}.pending`);\n try {\n cpSync(capture.directory, staging, { recursive: true, errorOnExist: true, force: false });\n this.saveMetadata({ ...capture, directory, meta: { ...capture.meta, persistent: true } }, staging);\n renameSync(staging, directory);\n } catch {\n rmSync(staging, { recursive: true, force: true });\n throw new Error(\"Persistent storage unavailable\");\n }\n // retain the original directory for active readers until capture deletion drains them\n capture.retiredDirectory = capture.directory;\n capture.directory = directory;\n capture.spool = join(directory, \"logs.ndjson\");\n capture.database = join(directory, \"logs.sqlite\");\n capture.meta.persistent = true;\n } else {\n if ([...this.captures.values()].filter((item) => !item.meta.persistent).length >= L.captures)\n throw new Error(\"Capture limit reached\");\n const directory = join(this.directory, capture.meta.id);\n const staging = `${directory}.pending`;\n try {\n cpSync(capture.directory, staging, { recursive: true, errorOnExist: true, force: false });\n rmSync(directory, { recursive: true, force: true });\n renameSync(staging, directory);\n rmSync(capture.directory, { recursive: true, force: true });\n } catch {\n rmSync(staging, { recursive: true, force: true });\n throw new Error(\"Temporary storage unavailable\");\n }\n capture.directory = directory;\n capture.retiredDirectory = undefined;\n capture.spool = join(directory, \"logs.ndjson\");\n capture.database = join(directory, \"logs.sqlite\");\n capture.meta = { ...capture.meta, persistent: false, expiresAt: Date.now() + L.ttlMs };\n }\n return { ...capture.meta };\n }\n if (request.operation === \"save-sql\") {\n if (typeof request.sql !== \"string\" || request.sql.length > 65536 || request.sql.includes(\"\\0\"))\n throw new Error(\"Invalid SQL draft\");\n const meta = { ...capture.meta, lastSql: request.sql };\n if (capture.meta.persistent) this.saveMetadata({ ...capture, meta });\n capture.meta = meta;\n return { ...capture.meta };\n }\n if (request.operation === \"export\") throw new Error(\"Export requires trusted destination\");\n if (request.operation === \"append\") {\n if (capture.meta.state !== \"collecting\") throw new Error(\"Capture is complete\");\n if (request.records.length > L.batchRows) throw new Error(\"Log batch exceeds limit\");\n const data = request.records\n .map((record) => JSON.stringify({ ...record, previous: record.previous === true }) + \"\\n\")\n .join(\"\");\n const bytes = Buffer.byteLength(data);\n if (\n bytes > L.batchBytes ||\n capture.meta.bytes + bytes > capture.limits.captureBytes ||\n capture.meta.rows + request.records.length > capture.limits.captureRows\n )\n throw new Error(\"Capture limit reached\");\n try {\n appendFileSync(capture.spool, data);\n } catch {\n truncateSync(capture.spool, capture.meta.bytes);\n throw new Error(\"Log capture storage unavailable\");\n }\n capture.meta.rows += request.records.length;\n capture.meta.bytes += bytes;\n return { ...capture.meta };\n }\n if (request.operation === \"finish\") {\n if (capture.meta.state !== \"collecting\") return { ...capture.meta };\n let database: DatabaseSync | undefined;\n try {\n writeFileSync(capture.database, \"\", { mode: 0o600 });\n database = new DatabaseSync(capture.database, { allowExtension: false });\n database.exec(\n \"PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA cache_size=-16384; PRAGMA temp_store=MEMORY\",\n );\n database.exec(\n \"CREATE TABLE logs(timestamp INTEGER, pod TEXT, podUid TEXT, container TEXT, level TEXT, message TEXT, fieldsJson TEXT, previous INTEGER CHECK(previous IN (0,1)))\",\n );\n const insert = database.prepare(\"INSERT INTO logs VALUES (?, ?, ?, ?, ?, ?, ?, ?)\");\n const input = createReadStream(capture.spool);\n const lines = createInterface({ input, crlfDelay: Infinity });\n let count = 0;\n database.exec(\"BEGIN\");\n try {\n for await (const line of lines) {\n const record = JSON.parse(line);\n insert.run(\n record.timestamp,\n record.pod,\n record.podUid,\n record.container,\n record.level,\n record.message,\n record.fieldsJson,\n record.previous ? 1 : 0,\n );\n if (++count % 5000 === 0) {\n database.exec(\"COMMIT; BEGIN\");\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n database.exec(\"COMMIT\");\n } finally {\n lines.close();\n input.destroy();\n }\n database.exec(\"CREATE INDEX logs_timestamp ON logs(timestamp)\");\n } catch {\n database?.close();\n database = undefined;\n rmSync(capture.database, { force: true });\n throw new Error(\"Log capture storage unavailable\");\n } finally {\n database?.close();\n }\n capture.meta.state = request.partial ? \"partial\" : \"ready\";\n capture.meta.notes = request.notes;\n return { ...capture.meta };\n }\n if (capture.meta.state === \"collecting\") throw new Error(\"Finish capture before querying\");\n return {\n database: capture.database,\n captureId: capture.meta.id,\n expiresAt: capture.meta.persistent ? Number.MAX_SAFE_INTEGER : capture.meta.expiresAt,\n } satisfies PowerLogsQuerySnapshot;\n }\n private saveMetadata(capture: Capture, directory = capture.directory): void {\n const temporary = join(directory, \"capture.json.pending\");\n writeFileSync(\n temporary,\n JSON.stringify({ meta: capture.meta, partition: capture.partition, limits: capture.limits }),\n { mode: 0o600 },\n );\n renameSync(temporary, join(directory, \"capture.json\"));\n }\n close(): void {\n this.closed = true;\n clearInterval(this.timer);\n for (const [id, capture] of this.captures) {\n if (!capture.meta.persistent) this.remove(id);\n }\n this.captures.clear();\n rmSync(this.directory, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
|
|
@@ -54,9 +54,9 @@ function validateLogRequest(input) {
|
|
|
54
54
|
};
|
|
55
55
|
return fail();
|
|
56
56
|
}
|
|
57
|
-
if (r.operation === "list") return {
|
|
57
|
+
if (r.operation === "list" || r.operation === "clear-persistent") return {
|
|
58
58
|
...partition,
|
|
59
|
-
operation:
|
|
59
|
+
operation: r.operation
|
|
60
60
|
};
|
|
61
61
|
if (r.operation === "cancel") {
|
|
62
62
|
if (!identifier(r.queryId)) return fail();
|
|
@@ -97,6 +97,14 @@ function validateLogRequest(input) {
|
|
|
97
97
|
...scoped,
|
|
98
98
|
operation: r.operation
|
|
99
99
|
};
|
|
100
|
+
if (r.operation === "set-persistent") {
|
|
101
|
+
if (typeof r.persistent !== "boolean") throw new Error("Invalid persistence flag");
|
|
102
|
+
return {
|
|
103
|
+
...scoped,
|
|
104
|
+
operation: "set-persistent",
|
|
105
|
+
persistent: r.persistent
|
|
106
|
+
};
|
|
107
|
+
}
|
|
100
108
|
if (r.operation === "save-sql") {
|
|
101
109
|
if (!text(r.sql, 65536)) return fail();
|
|
102
110
|
return {
|
|
@@ -262,7 +270,8 @@ function startPowerLogs() {
|
|
|
262
270
|
}, destination);
|
|
263
271
|
if (!allowed() || fingerprint(request) !== contextId) throw new Error("Log context changed");
|
|
264
272
|
return result;
|
|
265
|
-
} catch {
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (error instanceof Error && error.message === "Saved snapshot limit reached. Delete a saved snapshot before keeping another.") throw error;
|
|
266
275
|
throw new Error("Power Logs operation failed or is unavailable");
|
|
267
276
|
}
|
|
268
277
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-logs-ipc.js","names":[],"sources":["../../src/main/power-logs-ipc.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { Main } from \"@freelensapp/extensions\";\nimport { BrowserWindow, dialog, ipcMain } from \"electron\";\nimport { reaction } from \"mobx\";\nimport {\n POWER_LOGS_LIMITS as L,\n type LogRecord,\n type LogRequest,\n type LogTarget,\n POWER_LOGS_CHANNEL,\n} from \"../common/power-logs\";\nimport { applyPowerLogsLimits, normalizePowerLogsLimits } from \"../common/power-logs-limits\";\nimport { LogsPreferencesStore } from \"../common/store/logs-preferences-store\";\nimport {\n acquirePowerLogStream,\n cancelPowerLogPermits,\n releasePowerLogStream,\n renewPowerLogStream,\n requestPowerLogPermit,\n startPowerLogBudget,\n} from \"./power-logs-budget\";\nimport { PowerLogsWorker, sweepPowerLogsStorage } from \"./power-logs-worker\";\nimport { trustedSearchSender } from \"./resource-search\";\n\nconst text = (value: unknown, limit: number): value is string =>\n typeof value === \"string\" && value.length <= limit && !/[\\x00]/.test(value);\nconst identifier = (value: unknown): value is string => text(value, 253) && /^[a-zA-Z0-9_.:-]+$/.test(value);\nconst fail = (): never => {\n throw new Error(\"Invalid log request\");\n};\nexport function validateLogRequest(input: unknown): LogRequest {\n if (!input || typeof input !== \"object\") return fail();\n const r = input as LogRequest;\n if (!identifier(r.clusterId) || !text(r.contextId, 512) || !r.contextId) return fail();\n const partition = { clusterId: r.clusterId, contextId: r.contextId };\n if (r.operation === \"stream-open\" || r.operation === \"stream-close\" || r.operation === \"stream-renew\") {\n if (!identifier(r.streamId)) return fail();\n if (r.operation === \"stream-open\") {\n if (typeof r.follow !== \"boolean\") return fail();\n return { ...partition, operation: \"stream-open\", streamId: r.streamId, follow: r.follow };\n }\n return { ...partition, operation: r.operation, streamId: r.streamId };\n }\n if (r.operation === \"permit\") {\n if (r.lane === \"start\" && r.bytes === undefined) return { ...partition, operation: \"permit\", lane: \"start\" };\n if (r.lane === \"bytes\" && Number.isSafeInteger(r.bytes) && r.bytes! > 0 && r.bytes! <= L.batchBytes)\n return { ...partition, operation: \"permit\", lane: \"bytes\", bytes: r.bytes };\n return fail();\n }\n if (r.operation === \"list\") return { ...partition, operation: \"list\" };\n if (r.operation === \"cancel\") {\n if (!identifier(r.queryId)) return fail();\n return { ...partition, operation: \"cancel\", queryId: r.queryId };\n }\n if (r.operation === \"create\") {\n const t = r.target as LogTarget | undefined;\n if (\n !t ||\n ![\"Pod\", \"Deployment\", \"DaemonSet\", \"StatefulSet\", \"ReplicaSet\", \"Job\", \"CronJob\"].includes(t.kind) ||\n !identifier(t.namespace) ||\n !identifier(t.name) ||\n !identifier(t.uid)\n )\n return fail();\n return {\n ...partition,\n operation: \"create\",\n target: { kind: t.kind, namespace: t.namespace, name: t.name, uid: t.uid },\n };\n }\n if (!(\"captureId\" in r) || !identifier(r.captureId)) return fail();\n const scoped = { ...partition, captureId: r.captureId };\n if (r.operation === \"delete\" || r.operation === \"export\") return { ...scoped, operation: r.operation };\n if (r.operation === \"save-sql\") {\n if (!text(r.sql, 65536)) return fail();\n return { ...scoped, operation: \"save-sql\", sql: r.sql };\n }\n if (r.operation === \"query\") {\n if (!identifier(r.queryId) || !text(r.sql, 65536) || !r.sql.trim()) return fail();\n return { ...scoped, operation: \"query\", queryId: r.queryId, sql: r.sql };\n }\n if (r.operation === \"finish\") {\n if (\n typeof r.partial !== \"boolean\" ||\n !Array.isArray(r.notes) ||\n r.notes.length > 20 ||\n r.notes.some((n) => !text(n, 512))\n )\n return fail();\n return { ...scoped, operation: \"finish\", partial: r.partial, notes: [...r.notes] };\n }\n if (r.operation !== \"append\" || !Array.isArray(r.records) || r.records.length > L.batchRows) return fail();\n const records: LogRecord[] = r.records.map((record) => {\n if (\n !record ||\n typeof record !== \"object\" ||\n (record.timestamp !== null && (!Number.isSafeInteger(record.timestamp) || record.timestamp < 0)) ||\n !identifier(record.pod) ||\n !identifier(record.podUid) ||\n !identifier(record.container) ||\n (record.previous !== undefined && typeof record.previous !== \"boolean\") ||\n ![\"trace\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\", \"unknown\"].includes(record.level) ||\n !text(record.message, L.recordBytes) ||\n (record.fieldsJson !== null && !text(record.fieldsJson, L.recordBytes))\n )\n return fail();\n const projected = {\n timestamp: record.timestamp,\n pod: record.pod,\n podUid: record.podUid,\n container: record.container,\n level: record.level,\n message: record.message,\n fieldsJson: record.fieldsJson,\n previous: record.previous === true,\n };\n if (Buffer.byteLength(JSON.stringify(projected)) > L.recordBytes) return fail();\n return projected;\n });\n if (Buffer.byteLength(JSON.stringify(records)) > L.batchBytes) return fail();\n return { ...scoped, operation: \"append\", records };\n}\n\nexport function startPowerLogs(): () => void {\n let storageAvailable = true;\n try {\n sweepPowerLogsStorage();\n } catch {\n storageAvailable = false;\n }\n startPowerLogBudget();\n let active = true,\n worker: PowerLogsWorker | undefined;\n const streamOwners = new Map<\n string,\n { owner: string; contextId: string; clusterId: string; streamId: string; expiresAt: number }\n >();\n const dropExpiredOwners = () => {\n for (const [key, value] of streamOwners)\n if (value.expiresAt <= Date.now()) {\n releasePowerLogStream(value.clusterId, value.streamId);\n streamOwners.delete(key);\n }\n };\n const preferences = LogsPreferencesStore.getInstanceOrCreate<LogsPreferencesStore>();\n const stop = reaction(\n () => ({ enabled: preferences.enabled, limits: normalizePowerLogsLimits(preferences.limits) }),\n ({ enabled, limits }) => {\n applyPowerLogsLimits(limits);\n if (!enabled) {\n cancelPowerLogPermits();\n streamOwners.clear();\n worker?.close();\n worker = undefined;\n }\n },\n { fireImmediately: true },\n );\n const fingerprint = (r: LogRequest): string => {\n const cluster = Main.Catalog.getClusterById(r.clusterId);\n if (!cluster) throw new Error(\"Log cluster unavailable\");\n return createHash(\"sha256\")\n .update(r.contextId)\n .update(\"\\0\")\n .update(cluster.contextName)\n .update(\"\\0\")\n .update(cluster.kubeConfigPath)\n .update(\"\\0\")\n .update(readFileSync(cluster.kubeConfigPath))\n .digest(\"hex\");\n };\n ipcMain.handle(POWER_LOGS_CHANNEL, async (event, input: unknown) => {\n try {\n const request = validateLogRequest(input);\n const allowed = () => active && preferences.enabled && trustedSearchSender(event, request.clusterId);\n if (!allowed()) throw new Error(\"Power Logs is disabled or unavailable\");\n const contextId = fingerprint(request);\n if (\n request.operation === \"stream-open\" ||\n request.operation === \"stream-close\" ||\n request.operation === \"stream-renew\"\n ) {\n dropExpiredOwners();\n const key = JSON.stringify([request.clusterId, request.streamId]);\n const owner = `${event.sender.id}:${event.senderFrame?.processId}:${event.senderFrame?.routingId}`;\n if (request.operation === \"stream-open\") {\n if (streamOwners.has(key)) throw new Error(\"Log stream already exists\");\n const entry = {\n owner,\n contextId,\n clusterId: request.clusterId,\n streamId: request.streamId,\n expiresAt: Date.now() + 35000,\n };\n streamOwners.set(key, entry);\n try {\n await acquirePowerLogStream(request.clusterId, request.streamId, request.follow);\n if (!allowed() || fingerprint(request) !== contextId) throw new Error(\"Log context changed\");\n entry.expiresAt = Date.now() + 35000;\n } catch (error) {\n if (streamOwners.get(key) === entry) {\n streamOwners.delete(key);\n releasePowerLogStream(request.clusterId, request.streamId);\n }\n throw error;\n }\n } else {\n const entry = streamOwners.get(key);\n if (!entry || entry.owner !== owner) throw new Error(\"Log stream owner changed\");\n if (request.operation === \"stream-close\") {\n streamOwners.delete(key);\n releasePowerLogStream(request.clusterId, request.streamId);\n } else {\n if (entry.contextId !== contextId) throw new Error(\"Log context changed\");\n renewPowerLogStream(request.clusterId, request.streamId);\n entry.expiresAt = Date.now() + 35000;\n }\n }\n return true;\n }\n if (request.operation === \"permit\") {\n await requestPowerLogPermit(request.clusterId, request.lane === \"bytes\" ? request.bytes : undefined);\n if (!allowed() || fingerprint(request) !== contextId) throw new Error(\"Log context changed\");\n return true;\n }\n if (!storageAvailable) throw new Error(\"Log storage unavailable\");\n let destination: string | undefined;\n if (request.operation === \"export\") {\n const window = BrowserWindow.fromWebContents(event.sender);\n if (!window) throw new Error(\"Log window unavailable\");\n const choice = await dialog.showSaveDialog(window, {\n title: \"Export captured logs\",\n defaultPath: \"logs.ndjson\",\n filters: [{ name: \"NDJSON\", extensions: [\"ndjson\"] }],\n });\n if (choice.canceled || !choice.filePath) return false;\n destination = choice.filePath;\n if (!allowed() || fingerprint(request) !== contextId) throw new Error(\"Log context changed\");\n }\n if (!worker || worker.isClosed) worker = new PowerLogsWorker();\n const result = await worker.call({ ...request, contextId }, destination);\n if (!allowed() || fingerprint(request) !== contextId) throw new Error(\"Log context changed\");\n return result;\n } catch {\n throw new Error(\"Power Logs operation failed or is unavailable\");\n }\n });\n return () => {\n active = false;\n cancelPowerLogPermits(true);\n streamOwners.clear();\n stop();\n ipcMain.removeHandler(POWER_LOGS_CHANNEL);\n worker?.close();\n worker = undefined;\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"power-logs-ipc.js","names":[],"sources":["../../src/main/power-logs-ipc.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { Main } from \"@freelensapp/extensions\";\nimport { BrowserWindow, dialog, ipcMain } from \"electron\";\nimport { reaction } from \"mobx\";\nimport {\n POWER_LOGS_LIMITS as L,\n type LogRecord,\n type LogRequest,\n type LogTarget,\n POWER_LOGS_CHANNEL,\n} from \"../common/power-logs\";\nimport { applyPowerLogsLimits, normalizePowerLogsLimits } from \"../common/power-logs-limits\";\nimport { LogsPreferencesStore } from \"../common/store/logs-preferences-store\";\nimport {\n acquirePowerLogStream,\n cancelPowerLogPermits,\n releasePowerLogStream,\n renewPowerLogStream,\n requestPowerLogPermit,\n startPowerLogBudget,\n} from \"./power-logs-budget\";\nimport { PowerLogsWorker, sweepPowerLogsStorage } from \"./power-logs-worker\";\nimport { trustedSearchSender } from \"./resource-search\";\n\nconst text = (value: unknown, limit: number): value is string =>\n typeof value === \"string\" && value.length <= limit && !/[\\x00]/.test(value);\nconst identifier = (value: unknown): value is string => text(value, 253) && /^[a-zA-Z0-9_.:-]+$/.test(value);\nconst fail = (): never => {\n throw new Error(\"Invalid log request\");\n};\nexport function validateLogRequest(input: unknown): LogRequest {\n if (!input || typeof input !== \"object\") return fail();\n const r = input as LogRequest;\n if (!identifier(r.clusterId) || !text(r.contextId, 512) || !r.contextId) return fail();\n const partition = { clusterId: r.clusterId, contextId: r.contextId };\n if (r.operation === \"stream-open\" || r.operation === \"stream-close\" || r.operation === \"stream-renew\") {\n if (!identifier(r.streamId)) return fail();\n if (r.operation === \"stream-open\") {\n if (typeof r.follow !== \"boolean\") return fail();\n return { ...partition, operation: \"stream-open\", streamId: r.streamId, follow: r.follow };\n }\n return { ...partition, operation: r.operation, streamId: r.streamId };\n }\n if (r.operation === \"permit\") {\n if (r.lane === \"start\" && r.bytes === undefined) return { ...partition, operation: \"permit\", lane: \"start\" };\n if (r.lane === \"bytes\" && Number.isSafeInteger(r.bytes) && r.bytes! > 0 && r.bytes! <= L.batchBytes)\n return { ...partition, operation: \"permit\", lane: \"bytes\", bytes: r.bytes };\n return fail();\n }\n if (r.operation === \"list\" || r.operation === \"clear-persistent\") return { ...partition, operation: r.operation };\n if (r.operation === \"cancel\") {\n if (!identifier(r.queryId)) return fail();\n return { ...partition, operation: \"cancel\", queryId: r.queryId };\n }\n if (r.operation === \"create\") {\n const t = r.target as LogTarget | undefined;\n if (\n !t ||\n ![\"Pod\", \"Deployment\", \"DaemonSet\", \"StatefulSet\", \"ReplicaSet\", \"Job\", \"CronJob\"].includes(t.kind) ||\n !identifier(t.namespace) ||\n !identifier(t.name) ||\n !identifier(t.uid)\n )\n return fail();\n return {\n ...partition,\n operation: \"create\",\n target: { kind: t.kind, namespace: t.namespace, name: t.name, uid: t.uid },\n };\n }\n if (!(\"captureId\" in r) || !identifier(r.captureId)) return fail();\n const scoped = { ...partition, captureId: r.captureId };\n if (r.operation === \"delete\" || r.operation === \"export\") return { ...scoped, operation: r.operation };\n if (r.operation === \"set-persistent\") {\n if (typeof r.persistent !== \"boolean\") throw new Error(\"Invalid persistence flag\");\n return { ...scoped, operation: \"set-persistent\", persistent: r.persistent };\n }\n if (r.operation === \"save-sql\") {\n if (!text(r.sql, 65536)) return fail();\n return { ...scoped, operation: \"save-sql\", sql: r.sql };\n }\n if (r.operation === \"query\") {\n if (!identifier(r.queryId) || !text(r.sql, 65536) || !r.sql.trim()) return fail();\n return { ...scoped, operation: \"query\", queryId: r.queryId, sql: r.sql };\n }\n if (r.operation === \"finish\") {\n if (\n typeof r.partial !== \"boolean\" ||\n !Array.isArray(r.notes) ||\n r.notes.length > 20 ||\n r.notes.some((n) => !text(n, 512))\n )\n return fail();\n return { ...scoped, operation: \"finish\", partial: r.partial, notes: [...r.notes] };\n }\n if (r.operation !== \"append\" || !Array.isArray(r.records) || r.records.length > L.batchRows) return fail();\n const records: LogRecord[] = r.records.map((record) => {\n if (\n !record ||\n typeof record !== \"object\" ||\n (record.timestamp !== null && (!Number.isSafeInteger(record.timestamp) || record.timestamp < 0)) ||\n !identifier(record.pod) ||\n !identifier(record.podUid) ||\n !identifier(record.container) ||\n (record.previous !== undefined && typeof record.previous !== \"boolean\") ||\n ![\"trace\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\", \"unknown\"].includes(record.level) ||\n !text(record.message, L.recordBytes) ||\n (record.fieldsJson !== null && !text(record.fieldsJson, L.recordBytes))\n )\n return fail();\n const projected = {\n timestamp: record.timestamp,\n pod: record.pod,\n podUid: record.podUid,\n container: record.container,\n level: record.level,\n message: record.message,\n fieldsJson: record.fieldsJson,\n previous: record.previous === true,\n };\n if (Buffer.byteLength(JSON.stringify(projected)) > L.recordBytes) return fail();\n return projected;\n });\n if (Buffer.byteLength(JSON.stringify(records)) > L.batchBytes) return fail();\n return { ...scoped, operation: \"append\", records };\n}\n\nexport function startPowerLogs(): () => void {\n let storageAvailable = true;\n try {\n sweepPowerLogsStorage();\n } catch {\n storageAvailable = false;\n }\n startPowerLogBudget();\n let active = true,\n worker: PowerLogsWorker | undefined;\n const streamOwners = new Map<\n string,\n { owner: string; contextId: string; clusterId: string; streamId: string; expiresAt: number }\n >();\n const dropExpiredOwners = () => {\n for (const [key, value] of streamOwners)\n if (value.expiresAt <= Date.now()) {\n releasePowerLogStream(value.clusterId, value.streamId);\n streamOwners.delete(key);\n }\n };\n const preferences = LogsPreferencesStore.getInstanceOrCreate<LogsPreferencesStore>();\n const stop = reaction(\n () => ({ enabled: preferences.enabled, limits: normalizePowerLogsLimits(preferences.limits) }),\n ({ enabled, limits }) => {\n applyPowerLogsLimits(limits);\n if (!enabled) {\n cancelPowerLogPermits();\n streamOwners.clear();\n worker?.close();\n worker = undefined;\n }\n },\n { fireImmediately: true },\n );\n const fingerprint = (r: LogRequest): string => {\n const cluster = Main.Catalog.getClusterById(r.clusterId);\n if (!cluster) throw new Error(\"Log cluster unavailable\");\n return createHash(\"sha256\")\n .update(r.contextId)\n .update(\"\\0\")\n .update(cluster.contextName)\n .update(\"\\0\")\n .update(cluster.kubeConfigPath)\n .update(\"\\0\")\n .update(readFileSync(cluster.kubeConfigPath))\n .digest(\"hex\");\n };\n ipcMain.handle(POWER_LOGS_CHANNEL, async (event, input: unknown) => {\n try {\n const request = validateLogRequest(input);\n const allowed = () => active && preferences.enabled && trustedSearchSender(event, request.clusterId);\n if (!allowed()) throw new Error(\"Power Logs is disabled or unavailable\");\n const contextId = fingerprint(request);\n if (\n request.operation === \"stream-open\" ||\n request.operation === \"stream-close\" ||\n request.operation === \"stream-renew\"\n ) {\n dropExpiredOwners();\n const key = JSON.stringify([request.clusterId, request.streamId]);\n const owner = `${event.sender.id}:${event.senderFrame?.processId}:${event.senderFrame?.routingId}`;\n if (request.operation === \"stream-open\") {\n if (streamOwners.has(key)) throw new Error(\"Log stream already exists\");\n const entry = {\n owner,\n contextId,\n clusterId: request.clusterId,\n streamId: request.streamId,\n expiresAt: Date.now() + 35000,\n };\n streamOwners.set(key, entry);\n try {\n await acquirePowerLogStream(request.clusterId, request.streamId, request.follow);\n if (!allowed() || fingerprint(request) !== contextId) throw new Error(\"Log context changed\");\n entry.expiresAt = Date.now() + 35000;\n } catch (error) {\n if (streamOwners.get(key) === entry) {\n streamOwners.delete(key);\n releasePowerLogStream(request.clusterId, request.streamId);\n }\n throw error;\n }\n } else {\n const entry = streamOwners.get(key);\n if (!entry || entry.owner !== owner) throw new Error(\"Log stream owner changed\");\n if (request.operation === \"stream-close\") {\n streamOwners.delete(key);\n releasePowerLogStream(request.clusterId, request.streamId);\n } else {\n if (entry.contextId !== contextId) throw new Error(\"Log context changed\");\n renewPowerLogStream(request.clusterId, request.streamId);\n entry.expiresAt = Date.now() + 35000;\n }\n }\n return true;\n }\n if (request.operation === \"permit\") {\n await requestPowerLogPermit(request.clusterId, request.lane === \"bytes\" ? request.bytes : undefined);\n if (!allowed() || fingerprint(request) !== contextId) throw new Error(\"Log context changed\");\n return true;\n }\n if (!storageAvailable) throw new Error(\"Log storage unavailable\");\n let destination: string | undefined;\n if (request.operation === \"export\") {\n const window = BrowserWindow.fromWebContents(event.sender);\n if (!window) throw new Error(\"Log window unavailable\");\n const choice = await dialog.showSaveDialog(window, {\n title: \"Export captured logs\",\n defaultPath: \"logs.ndjson\",\n filters: [{ name: \"NDJSON\", extensions: [\"ndjson\"] }],\n });\n if (choice.canceled || !choice.filePath) return false;\n destination = choice.filePath;\n if (!allowed() || fingerprint(request) !== contextId) throw new Error(\"Log context changed\");\n }\n if (!worker || worker.isClosed) worker = new PowerLogsWorker();\n const result = await worker.call({ ...request, contextId }, destination);\n if (!allowed() || fingerprint(request) !== contextId) throw new Error(\"Log context changed\");\n return result;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message === \"Saved snapshot limit reached. Delete a saved snapshot before keeping another.\"\n )\n throw error;\n throw new Error(\"Power Logs operation failed or is unavailable\");\n }\n });\n return () => {\n active = false;\n cancelPowerLogPermits(true);\n streamOwners.clear();\n stop();\n ipcMain.removeHandler(POWER_LOGS_CHANNEL);\n worker?.close();\n worker = undefined;\n };\n}\n"],"mappings":"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"}
|