@64mb/freelens-power 1.4.0 → 1.6.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 (107) hide show
  1. package/README.md +7 -4
  2. package/out/main/ai-ipc.js +34 -3
  3. package/out/main/ai-ipc.js.map +1 -1
  4. package/out/main/common/power-logs-limits.js +227 -0
  5. package/out/main/common/power-logs-limits.js.map +1 -0
  6. package/out/main/common/power-logs.js +32 -0
  7. package/out/main/common/power-logs.js.map +1 -0
  8. package/out/main/common/resource-search.js +61 -0
  9. package/out/main/common/resource-search.js.map +1 -0
  10. package/out/main/common/store/logs-preferences-store.js +45 -0
  11. package/out/main/common/store/logs-preferences-store.js.map +1 -0
  12. package/out/main/common/store/monitoring-preferences-store.js +5 -0
  13. package/out/main/common/store/monitoring-preferences-store.js.map +1 -1
  14. package/out/main/dashboard-icon-cache.js +1 -1
  15. package/out/main/index.js +6 -0
  16. package/out/main/index.js.map +1 -1
  17. package/out/main/power-logs-budget.js +126 -0
  18. package/out/main/power-logs-budget.js.map +1 -0
  19. package/out/main/power-logs-child.js +86 -0
  20. package/out/main/power-logs-child.js.map +1 -0
  21. package/out/main/power-logs-database.js +179 -0
  22. package/out/main/power-logs-database.js.map +1 -0
  23. package/out/main/power-logs-ipc.js +275 -0
  24. package/out/main/power-logs-ipc.js.map +1 -0
  25. package/out/main/power-logs-query-child.js +23 -0
  26. package/out/main/power-logs-query-child.js.map +1 -0
  27. package/out/main/power-logs-query.js +127 -0
  28. package/out/main/power-logs-query.js.map +1 -0
  29. package/out/main/power-logs-worker.js +215 -0
  30. package/out/main/power-logs-worker.js.map +1 -0
  31. package/out/main/resource-search-database.js +209 -0
  32. package/out/main/resource-search-database.js.map +1 -0
  33. package/out/main/resource-search-worker.js +56 -0
  34. package/out/main/resource-search-worker.js.map +1 -0
  35. package/out/main/resource-search.js +155 -0
  36. package/out/main/resource-search.js.map +1 -0
  37. package/out/renderer/ai-logs-sql.js +27 -0
  38. package/out/renderer/ai-logs-sql.js.map +1 -0
  39. package/out/renderer/ai-message.js +48 -27
  40. package/out/renderer/ai-message.js.map +1 -1
  41. package/out/renderer/ai-terminal-context.js +50 -40
  42. package/out/renderer/ai-terminal-context.js.map +1 -1
  43. package/out/renderer/ai.js +15 -2
  44. package/out/renderer/ai.js.map +1 -1
  45. package/out/renderer/appearance.js +2 -1
  46. package/out/renderer/appearance.js.map +1 -1
  47. package/out/renderer/gateway-relationships-client.js +107 -0
  48. package/out/renderer/gateway-relationships-client.js.map +1 -0
  49. package/out/renderer/index.js +67 -29
  50. package/out/renderer/index.js.map +1 -1
  51. package/out/renderer/power-log-json.js +29 -0
  52. package/out/renderer/power-log-json.js.map +1 -0
  53. package/out/renderer/power-logs-client.js +35 -0
  54. package/out/renderer/power-logs-client.js.map +1 -0
  55. package/out/renderer/power-logs-collector.js +316 -0
  56. package/out/renderer/power-logs-collector.js.map +1 -0
  57. package/out/renderer/power-logs-entrypoints.js +201 -0
  58. package/out/renderer/power-logs-entrypoints.js.map +1 -0
  59. package/out/renderer/power-logs-sql-row.js +52 -0
  60. package/out/renderer/power-logs-sql-row.js.map +1 -0
  61. package/out/renderer/power-logs-transport.js +122 -0
  62. package/out/renderer/power-logs-transport.js.map +1 -0
  63. package/out/renderer/power-logs.css.js +116 -0
  64. package/out/renderer/power-logs.css.js.map +1 -0
  65. package/out/renderer/power-logs.js +1352 -0
  66. package/out/renderer/power-logs.js.map +1 -0
  67. package/out/renderer/preferences/logs-preferences.js +93 -0
  68. package/out/renderer/preferences/logs-preferences.js.map +1 -0
  69. package/out/renderer/preferences/monitoring-preferences.js +10 -0
  70. package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
  71. package/out/renderer/preferences/preferences.js +24 -10
  72. package/out/renderer/preferences/preferences.js.map +1 -1
  73. package/out/renderer/resource-relationships.css.js +1 -1
  74. package/out/renderer/resource-relationships.css.js.map +1 -1
  75. package/out/renderer/resource-relationships.js +46 -8
  76. package/out/renderer/resource-relationships.js.map +1 -1
  77. package/out/renderer/resource-search-client.js +116 -0
  78. package/out/renderer/resource-search-client.js.map +1 -0
  79. package/out/renderer/resource-search-discovery.js +79 -0
  80. package/out/renderer/resource-search-discovery.js.map +1 -0
  81. package/out/renderer/resource-search-model.js +2 -1
  82. package/out/renderer/resource-search-model.js.map +1 -1
  83. package/out/renderer/resource-search-routing.js +78 -0
  84. package/out/renderer/resource-search-routing.js.map +1 -0
  85. package/out/renderer/resource-search-sync.js +199 -0
  86. package/out/renderer/resource-search-sync.js.map +1 -0
  87. package/out/renderer/resource-search.js +279 -54
  88. package/out/renderer/resource-search.js.map +1 -1
  89. package/out/renderer/src/common/gateway-relationships.js +122 -0
  90. package/out/renderer/src/common/gateway-relationships.js.map +1 -0
  91. package/out/renderer/src/common/power-log-parser.js +114 -0
  92. package/out/renderer/src/common/power-log-parser.js.map +1 -0
  93. package/out/renderer/src/common/power-logs-ai.js +56 -0
  94. package/out/renderer/src/common/power-logs-ai.js.map +1 -0
  95. package/out/renderer/src/common/power-logs-limits.js +227 -0
  96. package/out/renderer/src/common/power-logs-limits.js.map +1 -0
  97. package/out/renderer/src/common/power-logs.js +32 -0
  98. package/out/renderer/src/common/power-logs.js.map +1 -0
  99. package/out/renderer/src/common/resource-relationships.js +8 -0
  100. package/out/renderer/src/common/resource-relationships.js.map +1 -1
  101. package/out/renderer/src/common/resource-search.js +6 -0
  102. package/out/renderer/src/common/resource-search.js.map +1 -0
  103. package/out/renderer/src/common/store/logs-preferences-store.js +45 -0
  104. package/out/renderer/src/common/store/logs-preferences-store.js.map +1 -0
  105. package/out/renderer/src/common/store/monitoring-preferences-store.js +5 -0
  106. package/out/renderer/src/common/store/monitoring-preferences-store.js.map +1 -1
  107. package/package.json +8 -2
@@ -0,0 +1,275 @@
1
+ const require_extensions = require("./_virtual/_global-external_@freelensapp/extensions.js");
2
+ const require__global_external_mobx = require("./_virtual/_global-external_mobx.js");
3
+ const require_power_logs = require("./common/power-logs.js");
4
+ const require_power_logs_limits = require("./common/power-logs-limits.js");
5
+ const require_logs_preferences_store = require("./common/store/logs-preferences-store.js");
6
+ const require_power_logs_budget = require("./power-logs-budget.js");
7
+ const require_power_logs_worker = require("./power-logs-worker.js");
8
+ const require_resource_search = require("./resource-search.js");
9
+ let node_crypto = require("node:crypto");
10
+ let electron = require("electron");
11
+ let node_fs = require("node:fs");
12
+ //#region src/main/power-logs-ipc.ts
13
+ var text = (value, limit) => typeof value === "string" && value.length <= limit && !/[\x00]/.test(value);
14
+ var identifier = (value) => text(value, 253) && /^[a-zA-Z0-9_.:-]+$/.test(value);
15
+ var fail = () => {
16
+ throw new Error("Invalid log request");
17
+ };
18
+ function validateLogRequest(input) {
19
+ if (!input || typeof input !== "object") return fail();
20
+ const r = input;
21
+ if (!identifier(r.clusterId) || !text(r.contextId, 512) || !r.contextId) return fail();
22
+ const partition = {
23
+ clusterId: r.clusterId,
24
+ contextId: r.contextId
25
+ };
26
+ if (r.operation === "stream-open" || r.operation === "stream-close" || r.operation === "stream-renew") {
27
+ if (!identifier(r.streamId)) return fail();
28
+ if (r.operation === "stream-open") {
29
+ if (typeof r.follow !== "boolean") return fail();
30
+ return {
31
+ ...partition,
32
+ operation: "stream-open",
33
+ streamId: r.streamId,
34
+ follow: r.follow
35
+ };
36
+ }
37
+ return {
38
+ ...partition,
39
+ operation: r.operation,
40
+ streamId: r.streamId
41
+ };
42
+ }
43
+ if (r.operation === "permit") {
44
+ if (r.lane === "start" && r.bytes === void 0) return {
45
+ ...partition,
46
+ operation: "permit",
47
+ lane: "start"
48
+ };
49
+ if (r.lane === "bytes" && Number.isSafeInteger(r.bytes) && r.bytes > 0 && r.bytes <= require_power_logs.POWER_LOGS_LIMITS.batchBytes) return {
50
+ ...partition,
51
+ operation: "permit",
52
+ lane: "bytes",
53
+ bytes: r.bytes
54
+ };
55
+ return fail();
56
+ }
57
+ if (r.operation === "list") return {
58
+ ...partition,
59
+ operation: "list"
60
+ };
61
+ if (r.operation === "cancel") {
62
+ if (!identifier(r.queryId)) return fail();
63
+ return {
64
+ ...partition,
65
+ operation: "cancel",
66
+ queryId: r.queryId
67
+ };
68
+ }
69
+ if (r.operation === "create") {
70
+ const t = r.target;
71
+ if (!t || ![
72
+ "Pod",
73
+ "Deployment",
74
+ "DaemonSet",
75
+ "StatefulSet",
76
+ "ReplicaSet",
77
+ "Job",
78
+ "CronJob"
79
+ ].includes(t.kind) || !identifier(t.namespace) || !identifier(t.name) || !identifier(t.uid)) return fail();
80
+ return {
81
+ ...partition,
82
+ operation: "create",
83
+ target: {
84
+ kind: t.kind,
85
+ namespace: t.namespace,
86
+ name: t.name,
87
+ uid: t.uid
88
+ }
89
+ };
90
+ }
91
+ if (!("captureId" in r) || !identifier(r.captureId)) return fail();
92
+ const scoped = {
93
+ ...partition,
94
+ captureId: r.captureId
95
+ };
96
+ if (r.operation === "delete" || r.operation === "export") return {
97
+ ...scoped,
98
+ operation: r.operation
99
+ };
100
+ if (r.operation === "query") {
101
+ if (!identifier(r.queryId) || !text(r.sql, 65536) || !r.sql.trim()) return fail();
102
+ return {
103
+ ...scoped,
104
+ operation: "query",
105
+ queryId: r.queryId,
106
+ sql: r.sql
107
+ };
108
+ }
109
+ if (r.operation === "finish") {
110
+ if (typeof r.partial !== "boolean" || !Array.isArray(r.notes) || r.notes.length > 20 || r.notes.some((n) => !text(n, 512))) return fail();
111
+ return {
112
+ ...scoped,
113
+ operation: "finish",
114
+ partial: r.partial,
115
+ notes: [...r.notes]
116
+ };
117
+ }
118
+ if (r.operation !== "append" || !Array.isArray(r.records) || r.records.length > require_power_logs.POWER_LOGS_LIMITS.batchRows) return fail();
119
+ const records = r.records.map((record) => {
120
+ if (!record || typeof record !== "object" || record.timestamp !== null && (!Number.isSafeInteger(record.timestamp) || record.timestamp < 0) || !identifier(record.pod) || !identifier(record.podUid) || !identifier(record.container) || record.previous !== void 0 && typeof record.previous !== "boolean" || ![
121
+ "trace",
122
+ "debug",
123
+ "info",
124
+ "warn",
125
+ "error",
126
+ "fatal",
127
+ "unknown"
128
+ ].includes(record.level) || !text(record.message, require_power_logs.POWER_LOGS_LIMITS.recordBytes) || record.fieldsJson !== null && !text(record.fieldsJson, require_power_logs.POWER_LOGS_LIMITS.recordBytes)) return fail();
129
+ const projected = {
130
+ timestamp: record.timestamp,
131
+ pod: record.pod,
132
+ podUid: record.podUid,
133
+ container: record.container,
134
+ level: record.level,
135
+ message: record.message,
136
+ fieldsJson: record.fieldsJson,
137
+ previous: record.previous === true
138
+ };
139
+ if (Buffer.byteLength(JSON.stringify(projected)) > require_power_logs.POWER_LOGS_LIMITS.recordBytes) return fail();
140
+ return projected;
141
+ });
142
+ if (Buffer.byteLength(JSON.stringify(records)) > require_power_logs.POWER_LOGS_LIMITS.batchBytes) return fail();
143
+ return {
144
+ ...scoped,
145
+ operation: "append",
146
+ records
147
+ };
148
+ }
149
+ function startPowerLogs() {
150
+ let storageAvailable = true;
151
+ try {
152
+ require_power_logs_worker.sweepPowerLogsStorage();
153
+ } catch {
154
+ storageAvailable = false;
155
+ }
156
+ require_power_logs_budget.startPowerLogBudget();
157
+ let active = true, worker;
158
+ const streamOwners = /* @__PURE__ */ new Map();
159
+ const dropExpiredOwners = () => {
160
+ for (const [key, value] of streamOwners) if (value.expiresAt <= Date.now()) {
161
+ require_power_logs_budget.releasePowerLogStream(value.clusterId, value.streamId);
162
+ streamOwners.delete(key);
163
+ }
164
+ };
165
+ const preferences = require_logs_preferences_store.LogsPreferencesStore.getInstanceOrCreate();
166
+ const stop = require__global_external_mobx.reaction(() => ({
167
+ enabled: preferences.enabled,
168
+ limits: require_power_logs_limits.normalizePowerLogsLimits(preferences.limits)
169
+ }), ({ enabled, limits }) => {
170
+ require_power_logs_limits.applyPowerLogsLimits(limits);
171
+ if (!enabled) {
172
+ require_power_logs_budget.cancelPowerLogPermits();
173
+ streamOwners.clear();
174
+ worker?.close();
175
+ worker = void 0;
176
+ }
177
+ }, { fireImmediately: true });
178
+ const fingerprint = (r) => {
179
+ const cluster = require_extensions.Main.Catalog.getClusterById(r.clusterId);
180
+ if (!cluster) throw new Error("Log cluster unavailable");
181
+ return (0, node_crypto.createHash)("sha256").update(r.contextId).update("\0").update(cluster.contextName).update("\0").update(cluster.kubeConfigPath).update("\0").update((0, node_fs.readFileSync)(cluster.kubeConfigPath)).digest("hex");
182
+ };
183
+ electron.ipcMain.handle(require_power_logs.POWER_LOGS_CHANNEL, async (event, input) => {
184
+ try {
185
+ const request = validateLogRequest(input);
186
+ const allowed = () => active && preferences.enabled && require_resource_search.trustedSearchSender(event, request.clusterId);
187
+ if (!allowed()) throw new Error("Power Logs is disabled or unavailable");
188
+ const contextId = fingerprint(request);
189
+ if (request.operation === "stream-open" || request.operation === "stream-close" || request.operation === "stream-renew") {
190
+ dropExpiredOwners();
191
+ const key = JSON.stringify([request.clusterId, request.streamId]);
192
+ const owner = `${event.sender.id}:${event.senderFrame?.processId}:${event.senderFrame?.routingId}`;
193
+ if (request.operation === "stream-open") {
194
+ if (streamOwners.has(key)) throw new Error("Log stream already exists");
195
+ const entry = {
196
+ owner,
197
+ contextId,
198
+ clusterId: request.clusterId,
199
+ streamId: request.streamId,
200
+ expiresAt: Date.now() + 35e3
201
+ };
202
+ streamOwners.set(key, entry);
203
+ try {
204
+ await require_power_logs_budget.acquirePowerLogStream(request.clusterId, request.streamId, request.follow);
205
+ if (!allowed() || fingerprint(request) !== contextId) throw new Error("Log context changed");
206
+ entry.expiresAt = Date.now() + 35e3;
207
+ } catch (error) {
208
+ if (streamOwners.get(key) === entry) {
209
+ streamOwners.delete(key);
210
+ require_power_logs_budget.releasePowerLogStream(request.clusterId, request.streamId);
211
+ }
212
+ throw error;
213
+ }
214
+ } else {
215
+ const entry = streamOwners.get(key);
216
+ if (!entry || entry.owner !== owner) throw new Error("Log stream owner changed");
217
+ if (request.operation === "stream-close") {
218
+ streamOwners.delete(key);
219
+ require_power_logs_budget.releasePowerLogStream(request.clusterId, request.streamId);
220
+ } else {
221
+ if (entry.contextId !== contextId) throw new Error("Log context changed");
222
+ require_power_logs_budget.renewPowerLogStream(request.clusterId, request.streamId);
223
+ entry.expiresAt = Date.now() + 35e3;
224
+ }
225
+ }
226
+ return true;
227
+ }
228
+ if (request.operation === "permit") {
229
+ await require_power_logs_budget.requestPowerLogPermit(request.clusterId, request.lane === "bytes" ? request.bytes : void 0);
230
+ if (!allowed() || fingerprint(request) !== contextId) throw new Error("Log context changed");
231
+ return true;
232
+ }
233
+ if (!storageAvailable) throw new Error("Log storage unavailable");
234
+ let destination;
235
+ if (request.operation === "export") {
236
+ const window = electron.BrowserWindow.fromWebContents(event.sender);
237
+ if (!window) throw new Error("Log window unavailable");
238
+ const choice = await electron.dialog.showSaveDialog(window, {
239
+ title: "Export captured logs",
240
+ defaultPath: "logs.ndjson",
241
+ filters: [{
242
+ name: "NDJSON",
243
+ extensions: ["ndjson"]
244
+ }]
245
+ });
246
+ if (choice.canceled || !choice.filePath) return false;
247
+ destination = choice.filePath;
248
+ if (!allowed() || fingerprint(request) !== contextId) throw new Error("Log context changed");
249
+ }
250
+ if (!worker || worker.isClosed) worker = new require_power_logs_worker.PowerLogsWorker();
251
+ const result = await worker.call({
252
+ ...request,
253
+ contextId
254
+ }, destination);
255
+ if (!allowed() || fingerprint(request) !== contextId) throw new Error("Log context changed");
256
+ return result;
257
+ } catch {
258
+ throw new Error("Power Logs operation failed or is unavailable");
259
+ }
260
+ });
261
+ return () => {
262
+ active = false;
263
+ require_power_logs_budget.cancelPowerLogPermits(true);
264
+ streamOwners.clear();
265
+ stop();
266
+ electron.ipcMain.removeHandler(require_power_logs.POWER_LOGS_CHANNEL);
267
+ worker?.close();
268
+ worker = void 0;
269
+ };
270
+ }
271
+ //#endregion
272
+ exports.startPowerLogs = startPowerLogs;
273
+ exports.validateLogRequest = validateLogRequest;
274
+
275
+ //# sourceMappingURL=power-logs-ipc.js.map
@@ -0,0 +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 === \"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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"}
@@ -0,0 +1,23 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_power_logs_limits = require("./common/power-logs-limits.js");
3
+ const require_power_logs_query = require("./power-logs-query.js");
4
+ //#region src/main/power-logs-query-child.ts
5
+ var port = process.parentPort;
6
+ if (!port) throw new Error("Log query process requires parent");
7
+ port.on("message", ({ data }) => {
8
+ try {
9
+ require_power_logs_limits.applyPowerLogsLimits(data.limits);
10
+ port.postMessage({
11
+ id: data.id,
12
+ result: require_power_logs_query.runPowerLogsQuery(data.database, data.sql)
13
+ });
14
+ } catch {
15
+ port.postMessage({
16
+ id: data.id,
17
+ error: "Log query failed or exceeded its limit"
18
+ });
19
+ }
20
+ });
21
+ //#endregion
22
+
23
+ //# sourceMappingURL=power-logs-query-child.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power-logs-query-child.js","names":[],"sources":["../../src/main/power-logs-query-child.ts"],"sourcesContent":["import { applyPowerLogsLimits } from \"../common/power-logs-limits\";\nimport { runPowerLogsQuery } from \"./power-logs-query\";\n\nconst port = process.parentPort;\nif (!port) throw new Error(\"Log query process requires parent\");\nport.on(\"message\", ({ data }: { data: { id: number; database: string; sql: string; limits?: unknown } }) => {\n try {\n applyPowerLogsLimits(data.limits);\n port.postMessage({ id: data.id, result: runPowerLogsQuery(data.database, data.sql) });\n } catch {\n port.postMessage({ id: data.id, error: \"Log query failed or exceeded its limit\" });\n }\n});\n"],"mappings":";;;;AAGA,IAAM,OAAO,QAAQ;AACrB,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,mCAAmC;AAC9D,KAAK,GAAG,YAAY,EAAE,WAAsF;CAC1G,IAAI;EACF,0BAAA,qBAAqB,KAAK,MAAM;EAChC,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI,QAAQ,yBAAA,kBAAkB,KAAK,UAAU,KAAK,GAAG;EAAE,CAAC;CACtF,QAAQ;EACN,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI,OAAO;EAAyC,CAAC;CACnF;AACF,CAAC"}
@@ -0,0 +1,127 @@
1
+ const require_power_logs = require("./common/power-logs.js");
2
+ let node_sqlite = require("node:sqlite");
3
+ //#region src/main/power-logs-query.ts
4
+ var functions = new Set(`abs avg ceil ceiling char coalesce concat concat_ws count date datetime floor format glob group_concat hex if ifnull iif instr json json_array json_array_length json_error_position json_extract json_group_array json_group_object json_insert json_object json_patch json_pretty json_quote json_remove json_replace json_set json_type json_valid likelihood likely lower ltrim max min nullif octet_length pow power printf quote replace round rtrim sign strftime string_agg substr substring sum time timediff total trim typeof unicode unixepoch unlikely upper zeroblob length rank dense_rank row_number percent_rank cume_dist ntile lag lead first_value last_value nth_value`.split(" "));
5
+ function singleStatement(sql) {
6
+ let ended = false;
7
+ let intent = "";
8
+ for (let i = 0; i < sql.length;) {
9
+ const c = sql[i];
10
+ if (/\s/.test(c)) {
11
+ i++;
12
+ continue;
13
+ }
14
+ if (sql.startsWith("--", i)) {
15
+ const end = sql.indexOf("\n", i + 2);
16
+ i = end < 0 ? sql.length : end + 1;
17
+ continue;
18
+ }
19
+ if (sql.startsWith("/*", i)) {
20
+ const end = sql.indexOf("*/", i + 2);
21
+ if (end < 0) return false;
22
+ i = end + 2;
23
+ continue;
24
+ }
25
+ if (ended) return false;
26
+ if (!intent) {
27
+ const word = /^[a-z]+/i.exec(sql.slice(i));
28
+ if (!word || !/^(select|with|explain)$/i.test(word[0])) return false;
29
+ intent = word[0];
30
+ }
31
+ if (c === ";") {
32
+ ended = true;
33
+ i++;
34
+ continue;
35
+ }
36
+ if (c === "'" || c === "\"" || c === "`" || c === "[") {
37
+ const closing = c === "[" ? "]" : c;
38
+ let closed = false;
39
+ for (i++; i < sql.length; i++) {
40
+ if (sql[i] !== closing) continue;
41
+ if (closing !== "]" && sql[i + 1] === closing) {
42
+ i++;
43
+ continue;
44
+ }
45
+ i++;
46
+ closed = true;
47
+ break;
48
+ }
49
+ if (!closed) return false;
50
+ continue;
51
+ }
52
+ i++;
53
+ }
54
+ return Boolean(intent);
55
+ }
56
+ function cell(value) {
57
+ if (value == null) return null;
58
+ if (typeof value === "bigint") return value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value.toString();
59
+ if (value instanceof Uint8Array) return Buffer.from(value).toString("hex");
60
+ if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
61
+ return typeof value === "string" ? value : String(value);
62
+ }
63
+ function runPowerLogsQuery(path, sql) {
64
+ if (!singleStatement(sql)) throw new Error("Use a single SELECT or EXPLAIN statement");
65
+ const started = Date.now();
66
+ const database = new node_sqlite.DatabaseSync(path, {
67
+ readOnly: true,
68
+ allowExtension: false
69
+ });
70
+ try {
71
+ if (typeof database.setAuthorizer !== "function") return {
72
+ columns: [],
73
+ rows: [],
74
+ truncated: false,
75
+ elapsedMs: Date.now() - started,
76
+ error: "Update Freelens to use secure Power Logs SQL queries. This runtime does not support SQL authorization."
77
+ };
78
+ database.exec("PRAGMA hard_heap_limit=134217728; PRAGMA cache_size=-16384; PRAGMA temp_store=MEMORY; PRAGMA mmap_size=0");
79
+ database.setAuthorizer((action, table, name, db) => {
80
+ if (action === node_sqlite.constants.SQLITE_SELECT || action === node_sqlite.constants.SQLITE_RECURSIVE) return node_sqlite.constants.SQLITE_OK;
81
+ if (action === node_sqlite.constants.SQLITE_READ && (db === "main" || db === null) && table === "logs") return node_sqlite.constants.SQLITE_OK;
82
+ if (action === node_sqlite.constants.SQLITE_READ && db === null && name === "" && table && !/^(sqlite_|pragma_)/i.test(table)) return node_sqlite.constants.SQLITE_OK;
83
+ if (action === node_sqlite.constants.SQLITE_FUNCTION && functions.has((name ?? "").toLowerCase())) return node_sqlite.constants.SQLITE_OK;
84
+ return node_sqlite.constants.SQLITE_DENY;
85
+ });
86
+ const statement = database.prepare(sql);
87
+ statement.setReadBigInts(true);
88
+ statement.setReturnArrays(true);
89
+ const columns = statement.columns().map((column) => column.name);
90
+ let bytes = Buffer.byteLength(JSON.stringify(columns));
91
+ if (columns.length > 100 || bytes > require_power_logs.POWER_LOGS_LIMITS.resultBytes) throw new Error("Query result exceeds limit");
92
+ const rows = [];
93
+ let truncated = false;
94
+ for (const row of statement.iterate()) {
95
+ const converted = row.map(cell);
96
+ const size = Buffer.byteLength(JSON.stringify(converted));
97
+ if (rows.length >= require_power_logs.POWER_LOGS_LIMITS.resultRows || bytes + size > require_power_logs.POWER_LOGS_LIMITS.resultBytes) {
98
+ truncated = true;
99
+ break;
100
+ }
101
+ rows.push(converted);
102
+ bytes += size;
103
+ }
104
+ return {
105
+ columns,
106
+ rows,
107
+ truncated,
108
+ elapsedMs: Date.now() - started
109
+ };
110
+ } catch (error) {
111
+ const message = error instanceof Error ? error.message : "";
112
+ if (/not authorized|authorization denied|prohibited/i.test(message)) throw new Error("Use read-only SELECT or EXPLAIN statements over logs");
113
+ return {
114
+ columns: [],
115
+ rows: [],
116
+ truncated: false,
117
+ elapsedMs: Date.now() - started,
118
+ error: "SQLite could not run this query. Check SQL syntax, column names and supported functions."
119
+ };
120
+ } finally {
121
+ database.close();
122
+ }
123
+ }
124
+ //#endregion
125
+ exports.runPowerLogsQuery = runPowerLogsQuery;
126
+
127
+ //# sourceMappingURL=power-logs-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power-logs-query.js","names":[],"sources":["../../src/main/power-logs-query.ts"],"sourcesContent":["import { constants, DatabaseSync } from \"node:sqlite\";\nimport { POWER_LOGS_LIMITS as L, type LogCell, type LogQueryResult } from \"../common/power-logs\";\n\nexport type PowerLogsQuerySnapshot = { database: string; captureId: string; expiresAt: number };\n\nconst functions = new Set(\n `abs avg ceil ceiling char coalesce concat concat_ws count date datetime floor format glob group_concat hex if ifnull iif instr json json_array json_array_length json_error_position json_extract json_group_array json_group_object json_insert json_object json_patch json_pretty json_quote json_remove json_replace json_set json_type json_valid likelihood likely lower ltrim max min nullif octet_length pow power printf quote replace round rtrim sign strftime string_agg substr substring sum time timediff total trim typeof unicode unixepoch unlikely upper zeroblob length rank dense_rank row_number percent_rank cume_dist ntile lag lead first_value last_value nth_value`.split(\n \" \",\n ),\n);\n\n// the scanner treats quoted text and comments as opaque before counting statements\nfunction singleStatement(sql: string): boolean {\n let ended = false;\n let intent = \"\";\n for (let i = 0; i < sql.length; ) {\n const c = sql[i];\n if (/\\s/.test(c)) {\n i++;\n continue;\n }\n if (sql.startsWith(\"--\", i)) {\n const end = sql.indexOf(\"\\n\", i + 2);\n i = end < 0 ? sql.length : end + 1;\n continue;\n }\n if (sql.startsWith(\"/*\", i)) {\n const end = sql.indexOf(\"*/\", i + 2);\n if (end < 0) return false;\n i = end + 2;\n continue;\n }\n if (ended) return false;\n if (!intent) {\n const word = /^[a-z]+/i.exec(sql.slice(i));\n if (!word || !/^(select|with|explain)$/i.test(word[0])) return false;\n intent = word[0];\n }\n if (c === \";\") {\n ended = true;\n i++;\n continue;\n }\n if (c === \"'\" || c === '\"' || c === \"`\" || c === \"[\") {\n const closing = c === \"[\" ? \"]\" : c;\n let closed = false;\n for (i++; i < sql.length; i++) {\n if (sql[i] !== closing) continue;\n if (closing !== \"]\" && sql[i + 1] === closing) {\n i++;\n continue;\n }\n i++;\n closed = true;\n break;\n }\n if (!closed) return false;\n continue;\n }\n i++;\n }\n return Boolean(intent);\n}\n\nfunction cell(value: unknown): LogCell {\n if (value == null) return null;\n if (typeof value === \"bigint\")\n return value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)\n ? Number(value)\n : value.toString();\n if (value instanceof Uint8Array) return Buffer.from(value).toString(\"hex\");\n if (typeof value === \"number\") return Number.isFinite(value) ? value : String(value);\n return typeof value === \"string\" ? value : String(value);\n}\n\nexport function runPowerLogsQuery(path: string, sql: string): LogQueryResult {\n if (!singleStatement(sql)) throw new Error(\"Use a single SELECT or EXPLAIN statement\");\n const started = Date.now();\n const database = new DatabaseSync(path, { readOnly: true, allowExtension: false });\n try {\n if (typeof database.setAuthorizer !== \"function\")\n return {\n columns: [],\n rows: [],\n truncated: false,\n elapsedMs: Date.now() - started,\n error: \"Update Freelens to use secure Power Logs SQL queries. This runtime does not support SQL authorization.\",\n };\n database.exec(\n \"PRAGMA hard_heap_limit=134217728; PRAGMA cache_size=-16384; PRAGMA temp_store=MEMORY; PRAGMA mmap_size=0\",\n );\n database.setAuthorizer((action, table, name, db) => {\n if (action === constants.SQLITE_SELECT || action === constants.SQLITE_RECURSIVE) return constants.SQLITE_OK;\n if (action === constants.SQLITE_READ && (db === \"main\" || db === null) && table === \"logs\")\n return constants.SQLITE_OK;\n // SQLite uses an empty column and no database for constant projections from a CTE\n if (action === constants.SQLITE_READ && db === null && name === \"\" && table && !/^(sqlite_|pragma_)/i.test(table))\n return constants.SQLITE_OK;\n if (action === constants.SQLITE_FUNCTION && functions.has((name ?? \"\").toLowerCase())) return constants.SQLITE_OK;\n return constants.SQLITE_DENY;\n });\n const statement = database.prepare(sql);\n statement.setReadBigInts(true);\n statement.setReturnArrays(true);\n const columns = statement.columns().map((column) => column.name);\n let bytes = Buffer.byteLength(JSON.stringify(columns));\n if (columns.length > 100 || bytes > L.resultBytes) throw new Error(\"Query result exceeds limit\");\n const rows: LogCell[][] = [];\n let truncated = false;\n for (const row of statement.iterate()) {\n const converted = (row as unknown as unknown[]).map(cell);\n const size = Buffer.byteLength(JSON.stringify(converted));\n if (rows.length >= L.resultRows || bytes + size > L.resultBytes) {\n truncated = true;\n break;\n }\n rows.push(converted);\n bytes += size;\n }\n return { columns, rows, truncated, elapsedMs: Date.now() - started };\n } catch (error) {\n const message = error instanceof Error ? error.message : \"\";\n if (/not authorized|authorization denied|prohibited/i.test(message))\n throw new Error(\"Use read-only SELECT or EXPLAIN statements over logs\");\n return {\n columns: [],\n rows: [],\n truncated: false,\n elapsedMs: Date.now() - started,\n error: \"SQLite could not run this query. Check SQL syntax, column names and supported functions.\",\n };\n } finally {\n database.close();\n }\n}\n"],"mappings":"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"}