@peers-app/peers-ui 0.7.24 → 0.7.26
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/dist/components/router.d.ts +1 -0
- package/dist/components/router.js +1 -0
- package/dist/screens/data-explorer/data-explorer.d.ts +2 -0
- package/dist/screens/data-explorer/data-explorer.js +288 -0
- package/dist/screens/data-explorer/index.d.ts +4 -0
- package/dist/screens/data-explorer/index.js +21 -0
- package/dist/screens/data-explorer/query-executor.d.ts +2 -0
- package/dist/screens/data-explorer/query-executor.js +281 -0
- package/dist/screens/settings/settings-page.js +81 -4
- package/dist/system-apps/data-explorer.app.d.ts +2 -0
- package/dist/system-apps/data-explorer.app.js +9 -0
- package/dist/system-apps/index.d.ts +1 -0
- package/dist/system-apps/index.js +5 -1
- package/package.json +3 -3
- package/src/components/router.tsx +1 -0
- package/src/screens/data-explorer/data-explorer.tsx +402 -0
- package/src/screens/data-explorer/index.ts +7 -0
- package/src/screens/data-explorer/query-executor.tsx +431 -0
- package/src/screens/settings/settings-page.tsx +66 -3
- package/src/system-apps/data-explorer.app.ts +8 -0
- package/src/system-apps/index.ts +3 -0
|
@@ -3,6 +3,7 @@ import "../screens/console-logs/console-logs-list";
|
|
|
3
3
|
import "../screens/groups";
|
|
4
4
|
import "../screens/contacts";
|
|
5
5
|
import "../screens/network-viewer";
|
|
6
|
+
import "../screens/data-explorer";
|
|
6
7
|
export declare function Router({ path: providedPath }?: {
|
|
7
8
|
path?: string;
|
|
8
9
|
}): React.JSX.Element;
|
|
@@ -74,6 +74,7 @@ require("../screens/console-logs/console-logs-list");
|
|
|
74
74
|
require("../screens/groups");
|
|
75
75
|
require("../screens/contacts");
|
|
76
76
|
require("../screens/network-viewer");
|
|
77
|
+
require("../screens/data-explorer");
|
|
77
78
|
function Router({ path: providedPath } = {}) {
|
|
78
79
|
try {
|
|
79
80
|
// Use provided path or fall back to global mainContentPath
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DataExplorerList = DataExplorerList;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const loading_indicator_1 = require("../../components/loading-indicator");
|
|
39
|
+
const ui_loader_1 = require("../../ui-router/ui-loader");
|
|
40
|
+
const globals_1 = require("../../globals");
|
|
41
|
+
function DataExplorerList() {
|
|
42
|
+
const [tables, setTables] = (0, react_1.useState)([]);
|
|
43
|
+
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
44
|
+
const [currentContext, setCurrentContext] = (0, react_1.useState)('');
|
|
45
|
+
const [compacting, setCompacting] = (0, react_1.useState)(false);
|
|
46
|
+
const formatBytes = (bytes) => {
|
|
47
|
+
if (bytes === undefined)
|
|
48
|
+
return '-';
|
|
49
|
+
if (bytes === 0)
|
|
50
|
+
return '0 B';
|
|
51
|
+
const k = 1024;
|
|
52
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
53
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
54
|
+
return Math.round(bytes / Math.pow(k, i)) + ' ' + sizes[i];
|
|
55
|
+
};
|
|
56
|
+
const getBytesColorClass = (bytes) => {
|
|
57
|
+
if (bytes === undefined)
|
|
58
|
+
return 'text-secondary';
|
|
59
|
+
const kb = 1024;
|
|
60
|
+
const mb = kb * 1024;
|
|
61
|
+
if (bytes < 10 * kb)
|
|
62
|
+
return 'text-secondary'; // 0 to 10KB: secondary (gray)
|
|
63
|
+
if (bytes < 10 * mb)
|
|
64
|
+
return 'text-primary'; // 10KB to 10MB: primary (blue)
|
|
65
|
+
if (bytes < 100 * mb)
|
|
66
|
+
return 'text-warning'; // 10MB to 100MB: warning (orange)
|
|
67
|
+
return 'text-danger'; // >100MB: danger (red)
|
|
68
|
+
};
|
|
69
|
+
const loadTables = async () => {
|
|
70
|
+
try {
|
|
71
|
+
if (!(0, globals_1.isDesktop)())
|
|
72
|
+
return;
|
|
73
|
+
const api = window.electronAPI?.dataExplorer;
|
|
74
|
+
if (!api) {
|
|
75
|
+
console.warn('Data Explorer API not available');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Get current context info
|
|
79
|
+
const contextInfo = await api.getCurrentContext();
|
|
80
|
+
setCurrentContext(contextInfo.contextName);
|
|
81
|
+
// Get all tables from IPC
|
|
82
|
+
const allTablesInfo = await api.getAllTables();
|
|
83
|
+
// Sort by registered status first, then by table name
|
|
84
|
+
allTablesInfo.sort((a, b) => {
|
|
85
|
+
if (a.isRegistered !== b.isRegistered) {
|
|
86
|
+
return a.isRegistered ? -1 : 1;
|
|
87
|
+
}
|
|
88
|
+
return a.tableName.localeCompare(b.tableName);
|
|
89
|
+
});
|
|
90
|
+
setTables(allTablesInfo);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error('Error loading tables:', error);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
setLoading(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const compactDatabase = async (beforeTimestamp) => {
|
|
100
|
+
try {
|
|
101
|
+
setCompacting(true);
|
|
102
|
+
const api = window.electronAPI?.dataExplorer;
|
|
103
|
+
if (!api) {
|
|
104
|
+
alert('Data Explorer API not available');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const isExtreme = beforeTimestamp !== undefined;
|
|
108
|
+
if (isExtreme) {
|
|
109
|
+
const confirmed = window.confirm('Extreme Compaction will remove ALL change history. This cannot be undone. Continue?');
|
|
110
|
+
if (!confirmed) {
|
|
111
|
+
setCompacting(false);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
await api.compactDatabase(beforeTimestamp);
|
|
116
|
+
alert(`Database compacted successfully!${isExtreme ? ' (Extreme mode)' : ''}`);
|
|
117
|
+
// Reload tables to show updated sizes
|
|
118
|
+
setLoading(true);
|
|
119
|
+
await loadTables();
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error('Error compacting database:', error);
|
|
123
|
+
alert(`Error compacting database: ${error.message || 'Unknown error'}`);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
setCompacting(false);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const resetChangeTracking = async () => {
|
|
130
|
+
try {
|
|
131
|
+
setCompacting(true);
|
|
132
|
+
const api = window.electronAPI?.dataExplorer;
|
|
133
|
+
if (!api) {
|
|
134
|
+
alert('Data Explorer API not available');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const confirmed = window.confirm('Reset Change Tracking will clear all change history and rebuild it from scratch. This may take a while. Continue?');
|
|
138
|
+
if (!confirmed) {
|
|
139
|
+
setCompacting(false);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
await api.resetChangeTracking();
|
|
143
|
+
alert('Change tracking reset successfully!');
|
|
144
|
+
// Reload tables to show updated sizes
|
|
145
|
+
setLoading(true);
|
|
146
|
+
await loadTables();
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error('Error resetting change tracking:', error);
|
|
150
|
+
alert(`Error resetting change tracking: ${error.message || 'Unknown error'}`);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
setCompacting(false);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
(0, react_1.useEffect)(() => {
|
|
157
|
+
loadTables();
|
|
158
|
+
}, []);
|
|
159
|
+
if (loading) {
|
|
160
|
+
return react_1.default.createElement(loading_indicator_1.LoadingIndicator, null);
|
|
161
|
+
}
|
|
162
|
+
if (!(0, globals_1.isDesktop)()) {
|
|
163
|
+
return (react_1.default.createElement("div", { className: "container-fluid p-4" },
|
|
164
|
+
react_1.default.createElement("div", { className: "alert alert-info" }, "Data Explorer is only available in the desktop application.")));
|
|
165
|
+
}
|
|
166
|
+
if (!currentContext && tables.length === 0) {
|
|
167
|
+
return (react_1.default.createElement("div", { className: "container-fluid p-4" },
|
|
168
|
+
react_1.default.createElement("div", { className: "alert alert-warning" }, "Unable to load database information.")));
|
|
169
|
+
}
|
|
170
|
+
return (react_1.default.createElement("div", { className: "container-fluid p-4" },
|
|
171
|
+
react_1.default.createElement("ul", { className: "nav nav-tabs mb-4" },
|
|
172
|
+
react_1.default.createElement("li", { className: "nav-item" },
|
|
173
|
+
react_1.default.createElement("a", { className: "nav-link active", href: "#/data-explorer" },
|
|
174
|
+
react_1.default.createElement("i", { className: "bi bi-table" }),
|
|
175
|
+
" Tables")),
|
|
176
|
+
react_1.default.createElement("li", { className: "nav-item" },
|
|
177
|
+
react_1.default.createElement("a", { className: "nav-link", href: "#/data-explorer/query" },
|
|
178
|
+
react_1.default.createElement("i", { className: "bi bi-terminal" }),
|
|
179
|
+
" Query"))),
|
|
180
|
+
react_1.default.createElement("div", { className: "d-flex justify-content-between align-items-center mb-4" },
|
|
181
|
+
react_1.default.createElement("h2", null, "Data Explorer"),
|
|
182
|
+
react_1.default.createElement("div", { className: "d-flex gap-2" },
|
|
183
|
+
react_1.default.createElement("div", { className: "btn-group", role: "group" },
|
|
184
|
+
react_1.default.createElement("button", { type: "button", className: "btn btn-sm btn-warning", onClick: () => compactDatabase(), disabled: compacting || loading, title: "Compact database (cleanup changes older than 2 weeks)" }, compacting ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
185
|
+
react_1.default.createElement("span", { className: "spinner-border spinner-border-sm me-1", role: "status", "aria-hidden": "true" }),
|
|
186
|
+
"Compacting...")) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
187
|
+
react_1.default.createElement("i", { className: "bi bi-disc" }),
|
|
188
|
+
" Compact Database"))),
|
|
189
|
+
react_1.default.createElement("button", { type: "button", className: "btn btn-sm btn-warning dropdown-toggle dropdown-toggle-split", "data-bs-toggle": "dropdown", "aria-expanded": "false", disabled: compacting || loading, title: "More compaction options" },
|
|
190
|
+
react_1.default.createElement("span", { className: "visually-hidden" }, "Toggle Dropdown")),
|
|
191
|
+
react_1.default.createElement("ul", { className: "dropdown-menu" },
|
|
192
|
+
react_1.default.createElement("li", null,
|
|
193
|
+
react_1.default.createElement("a", { className: "dropdown-item", href: "#", onClick: (e) => {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
compactDatabase(Date.now());
|
|
196
|
+
} },
|
|
197
|
+
react_1.default.createElement("i", { className: "bi bi-exclamation-triangle text-danger me-2" }),
|
|
198
|
+
"Extreme Compaction",
|
|
199
|
+
react_1.default.createElement("div", { className: "small text-muted" }, "Remove ALL change history"))),
|
|
200
|
+
react_1.default.createElement("li", null,
|
|
201
|
+
react_1.default.createElement("hr", { className: "dropdown-divider" })),
|
|
202
|
+
react_1.default.createElement("li", null,
|
|
203
|
+
react_1.default.createElement("a", { className: "dropdown-item", href: "#", onClick: (e) => {
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
resetChangeTracking();
|
|
206
|
+
} },
|
|
207
|
+
react_1.default.createElement("i", { className: "bi bi-arrow-clockwise text-warning me-2" }),
|
|
208
|
+
"Reset Change Tracking",
|
|
209
|
+
react_1.default.createElement("div", { className: "small text-muted" }, "Clear and rebuild all change history"))))),
|
|
210
|
+
react_1.default.createElement("button", { className: "btn btn-sm btn-primary", onClick: () => {
|
|
211
|
+
setLoading(true);
|
|
212
|
+
loadTables();
|
|
213
|
+
}, disabled: compacting },
|
|
214
|
+
react_1.default.createElement("i", { className: "bi bi-arrow-clockwise" }),
|
|
215
|
+
" Refresh"))),
|
|
216
|
+
react_1.default.createElement("div", { className: "card mb-4" },
|
|
217
|
+
react_1.default.createElement("div", { className: "card-body" },
|
|
218
|
+
react_1.default.createElement("div", { className: "d-flex justify-content-between align-items-center" },
|
|
219
|
+
react_1.default.createElement("div", null,
|
|
220
|
+
react_1.default.createElement("h5", { className: "card-title mb-2" }, "Current Data Context"),
|
|
221
|
+
react_1.default.createElement("p", { className: "mb-0" }, currentContext)),
|
|
222
|
+
react_1.default.createElement("div", { className: "text-end" },
|
|
223
|
+
react_1.default.createElement("div", { className: "text-muted small" }, "Total DB Size"),
|
|
224
|
+
react_1.default.createElement("div", { className: "fs-4 fw-bold" }, formatBytes(tables.reduce((sum, t) => sum + (t.bytes || 0), 0))))))),
|
|
225
|
+
react_1.default.createElement("div", { className: "card mb-4" },
|
|
226
|
+
react_1.default.createElement("div", { className: "card-body" },
|
|
227
|
+
react_1.default.createElement("h5", { className: "card-title" },
|
|
228
|
+
"Registered Tables (",
|
|
229
|
+
tables.filter(t => t.isRegistered).length,
|
|
230
|
+
")",
|
|
231
|
+
react_1.default.createElement("span", { className: "badge bg-success ms-2" }, "Active")),
|
|
232
|
+
react_1.default.createElement("p", { className: "text-muted small" }, "These tables are registered with the TableContainer and actively used by the application."),
|
|
233
|
+
tables.filter(t => t.isRegistered).length === 0 ? (react_1.default.createElement("p", { className: "text-muted" }, "No registered tables found")) : (react_1.default.createElement("div", { className: "table-responsive" },
|
|
234
|
+
react_1.default.createElement("table", { className: "table table-hover table-sm" },
|
|
235
|
+
react_1.default.createElement("thead", null,
|
|
236
|
+
react_1.default.createElement("tr", null,
|
|
237
|
+
react_1.default.createElement("th", null, "Table Name"),
|
|
238
|
+
react_1.default.createElement("th", null, "Table ID"),
|
|
239
|
+
react_1.default.createElement("th", { className: "text-end" }, "Row Count"),
|
|
240
|
+
react_1.default.createElement("th", { className: "text-end" }, "Bytes"))),
|
|
241
|
+
react_1.default.createElement("tbody", null, tables.filter(t => t.isRegistered).map(table => (react_1.default.createElement("tr", { key: table.tableName },
|
|
242
|
+
react_1.default.createElement("td", null,
|
|
243
|
+
react_1.default.createElement("strong", null, table.tableName)),
|
|
244
|
+
react_1.default.createElement("td", null,
|
|
245
|
+
react_1.default.createElement("code", { className: "text-muted small" }, table.tableId || 'system')),
|
|
246
|
+
react_1.default.createElement("td", { className: "text-end" }, table.rowCount !== undefined ? table.rowCount.toLocaleString() : '-'),
|
|
247
|
+
react_1.default.createElement("td", { className: `text-end fw-bold ${getBytesColorClass(table.bytes)}` }, formatBytes(table.bytes))))))))))),
|
|
248
|
+
tables.some(t => !t.isRegistered) && (react_1.default.createElement("div", { className: "card" },
|
|
249
|
+
react_1.default.createElement("div", { className: "card-body" },
|
|
250
|
+
react_1.default.createElement("h5", { className: "card-title" },
|
|
251
|
+
"Unregistered Tables (",
|
|
252
|
+
tables.filter(t => !t.isRegistered).length,
|
|
253
|
+
")",
|
|
254
|
+
react_1.default.createElement("span", { className: "badge bg-warning text-dark ms-2" }, "Raw SQLite")),
|
|
255
|
+
react_1.default.createElement("p", { className: "text-muted small" }, "These tables exist in the SQLite database but are not registered with the TableContainer. They may be system tables, change tracking tables, or legacy tables."),
|
|
256
|
+
react_1.default.createElement("div", { className: "table-responsive" },
|
|
257
|
+
react_1.default.createElement("table", { className: "table table-hover table-sm" },
|
|
258
|
+
react_1.default.createElement("thead", null,
|
|
259
|
+
react_1.default.createElement("tr", null,
|
|
260
|
+
react_1.default.createElement("th", null, "Table Name"),
|
|
261
|
+
react_1.default.createElement("th", null, "Type"),
|
|
262
|
+
react_1.default.createElement("th", { className: "text-end" }, "Row Count"),
|
|
263
|
+
react_1.default.createElement("th", { className: "text-end" }, "Bytes"),
|
|
264
|
+
react_1.default.createElement("th", null, "SQL Definition"))),
|
|
265
|
+
react_1.default.createElement("tbody", null, tables.filter(t => !t.isRegistered).map(table => (react_1.default.createElement("tr", { key: table.tableName },
|
|
266
|
+
react_1.default.createElement("td", null,
|
|
267
|
+
react_1.default.createElement("strong", null, table.tableName)),
|
|
268
|
+
react_1.default.createElement("td", null,
|
|
269
|
+
react_1.default.createElement("span", { className: "badge bg-secondary" }, table.type || 'table')),
|
|
270
|
+
react_1.default.createElement("td", { className: "text-end" }, table.rowCount !== undefined ? table.rowCount.toLocaleString() : '-'),
|
|
271
|
+
react_1.default.createElement("td", { className: `text-end fw-bold ${getBytesColorClass(table.bytes)}` }, formatBytes(table.bytes)),
|
|
272
|
+
react_1.default.createElement("td", null, table.sql ? (react_1.default.createElement("details", null,
|
|
273
|
+
react_1.default.createElement("summary", { className: "cursor-pointer text-primary", style: { cursor: 'pointer' } },
|
|
274
|
+
react_1.default.createElement("small", null, "View SQL")),
|
|
275
|
+
react_1.default.createElement("pre", { className: "mt-2 p-2 bg-body-secondary border rounded", style: { fontSize: '0.75rem', maxHeight: '300px', overflow: 'auto' } },
|
|
276
|
+
react_1.default.createElement("code", { className: "text-body" }, table.sql)))) : (react_1.default.createElement("span", { className: "text-muted small" }, "-"))))))))))))));
|
|
277
|
+
}
|
|
278
|
+
(0, ui_loader_1.registerInternalPeersUI)({
|
|
279
|
+
peersUIId: 'data-explorer-list-ui',
|
|
280
|
+
component: DataExplorerList,
|
|
281
|
+
routes: [
|
|
282
|
+
{
|
|
283
|
+
isMatch: (_props, context) => context.path === 'data-explorer',
|
|
284
|
+
uiCategory: 'screen',
|
|
285
|
+
priority: 2
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// Import all data explorer screen components to ensure they register their routes
|
|
18
|
+
require("./data-explorer");
|
|
19
|
+
require("./query-executor");
|
|
20
|
+
__exportStar(require("./data-explorer"), exports);
|
|
21
|
+
__exportStar(require("./query-executor"), exports);
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.QueryExecutor = QueryExecutor;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const loading_indicator_1 = require("../../components/loading-indicator");
|
|
39
|
+
const ui_loader_1 = require("../../ui-router/ui-loader");
|
|
40
|
+
const globals_1 = require("../../globals");
|
|
41
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
42
|
+
const defaultQuery = 'SELECT * FROM sqlite_master WHERE type=\'table\' LIMIT 10';
|
|
43
|
+
// Persistent vars at module level
|
|
44
|
+
const queryTabs = (0, peers_sdk_1.groupDeviceVar)('dataExplorerQueryTabs', {
|
|
45
|
+
defaultValue: [{
|
|
46
|
+
id: (0, peers_sdk_1.newid)(),
|
|
47
|
+
name: 'Query 1',
|
|
48
|
+
query: defaultQuery
|
|
49
|
+
}]
|
|
50
|
+
});
|
|
51
|
+
const activeQueryTabId = (0, peers_sdk_1.groupDeviceVar)('dataExplorerActiveQueryTab', {
|
|
52
|
+
defaultValue: ''
|
|
53
|
+
});
|
|
54
|
+
function QueryExecutor() {
|
|
55
|
+
const [, forceUpdate] = (0, react_1.useState)({});
|
|
56
|
+
const [results, setResults] = (0, react_1.useState)({});
|
|
57
|
+
const [errors, setErrors] = (0, react_1.useState)({});
|
|
58
|
+
const [loading, setLoading] = (0, react_1.useState)({});
|
|
59
|
+
// Subscribe to tab changes
|
|
60
|
+
(0, react_1.useEffect)(() => {
|
|
61
|
+
const subscription1 = queryTabs.subscribe(() => forceUpdate({}));
|
|
62
|
+
const subscription2 = activeQueryTabId.subscribe(() => forceUpdate({}));
|
|
63
|
+
// Initialize activeTabId if not set
|
|
64
|
+
if (!activeQueryTabId() && queryTabs().length > 0) {
|
|
65
|
+
activeQueryTabId(queryTabs()[0].id);
|
|
66
|
+
}
|
|
67
|
+
return () => {
|
|
68
|
+
subscription1.dispose();
|
|
69
|
+
subscription2.dispose();
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
const tabs = queryTabs();
|
|
73
|
+
const activeTabId = activeQueryTabId();
|
|
74
|
+
const activeTab = tabs.find(t => t.id === activeTabId);
|
|
75
|
+
const query = activeTab?.query || '';
|
|
76
|
+
const result = activeTab ? results[activeTab.id] : null;
|
|
77
|
+
const error = activeTab ? errors[activeTab.id] : null;
|
|
78
|
+
const isLoading = activeTab ? loading[activeTab.id] : false;
|
|
79
|
+
const setQuery = (newQuery) => {
|
|
80
|
+
if (!activeTab)
|
|
81
|
+
return;
|
|
82
|
+
const currentTabs = queryTabs();
|
|
83
|
+
queryTabs(currentTabs.map(t => t.id === activeTab.id ? { ...t, query: newQuery } : t));
|
|
84
|
+
};
|
|
85
|
+
const setResult = (result) => {
|
|
86
|
+
if (!activeTab)
|
|
87
|
+
return;
|
|
88
|
+
setResults(prev => {
|
|
89
|
+
const newResults = { ...prev };
|
|
90
|
+
if (result === null) {
|
|
91
|
+
delete newResults[activeTab.id];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
newResults[activeTab.id] = result;
|
|
95
|
+
}
|
|
96
|
+
return newResults;
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const setError = (error) => {
|
|
100
|
+
if (!activeTab)
|
|
101
|
+
return;
|
|
102
|
+
setErrors(prev => {
|
|
103
|
+
const newErrors = { ...prev };
|
|
104
|
+
if (error === null) {
|
|
105
|
+
delete newErrors[activeTab.id];
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
newErrors[activeTab.id] = error;
|
|
109
|
+
}
|
|
110
|
+
return newErrors;
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const setLoadingState = (isLoading) => {
|
|
114
|
+
if (!activeTab)
|
|
115
|
+
return;
|
|
116
|
+
setLoading(prev => ({ ...prev, [activeTab.id]: isLoading }));
|
|
117
|
+
};
|
|
118
|
+
const executeQuery = async () => {
|
|
119
|
+
if (!query.trim()) {
|
|
120
|
+
setError('Please enter a query');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
setLoadingState(true);
|
|
125
|
+
setError(null);
|
|
126
|
+
setResult(null);
|
|
127
|
+
const api = window.electronAPI?.dataExplorer;
|
|
128
|
+
if (!api) {
|
|
129
|
+
setError('Data Explorer API not available');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const queryResult = await api.executeQuery(query);
|
|
133
|
+
setResult(queryResult);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
setError(err.message || 'Query execution failed');
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
setLoadingState(false);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const addNewTab = () => {
|
|
143
|
+
const currentTabs = queryTabs();
|
|
144
|
+
const newTab = {
|
|
145
|
+
id: (0, peers_sdk_1.newid)(),
|
|
146
|
+
name: `Query ${currentTabs.length + 1}`,
|
|
147
|
+
query: defaultQuery
|
|
148
|
+
};
|
|
149
|
+
queryTabs([...currentTabs, newTab]);
|
|
150
|
+
activeQueryTabId(newTab.id);
|
|
151
|
+
};
|
|
152
|
+
const closeTab = (tabId) => {
|
|
153
|
+
const currentTabs = queryTabs();
|
|
154
|
+
if (currentTabs.length === 1)
|
|
155
|
+
return; // Don't close the last tab
|
|
156
|
+
const tabIndex = currentTabs.findIndex(t => t.id === tabId);
|
|
157
|
+
const newTabs = currentTabs.filter(t => t.id !== tabId);
|
|
158
|
+
queryTabs(newTabs);
|
|
159
|
+
// If we're closing the active tab, switch to another one
|
|
160
|
+
if (tabId === activeQueryTabId()) {
|
|
161
|
+
const newIndex = Math.min(tabIndex, newTabs.length - 1);
|
|
162
|
+
activeQueryTabId(newTabs[newIndex].id);
|
|
163
|
+
}
|
|
164
|
+
// Clean up results and errors for closed tab
|
|
165
|
+
setResults(prev => {
|
|
166
|
+
const newResults = { ...prev };
|
|
167
|
+
delete newResults[tabId];
|
|
168
|
+
return newResults;
|
|
169
|
+
});
|
|
170
|
+
setErrors(prev => {
|
|
171
|
+
const newErrors = { ...prev };
|
|
172
|
+
delete newErrors[tabId];
|
|
173
|
+
return newErrors;
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const renameTab = (tabId, newName) => {
|
|
177
|
+
const currentTabs = queryTabs();
|
|
178
|
+
queryTabs(currentTabs.map(t => t.id === tabId ? { ...t, name: newName } : t));
|
|
179
|
+
};
|
|
180
|
+
const handleKeyDown = (e) => {
|
|
181
|
+
// Execute on Cmd/Ctrl + Enter
|
|
182
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
executeQuery();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
if (!(0, globals_1.isDesktop)()) {
|
|
188
|
+
return (react_1.default.createElement("div", { className: "container-fluid p-4" },
|
|
189
|
+
react_1.default.createElement("div", { className: "alert alert-info" }, "Query Executor is only available in the desktop application.")));
|
|
190
|
+
}
|
|
191
|
+
return (react_1.default.createElement("div", { className: "container-fluid p-4" },
|
|
192
|
+
react_1.default.createElement("ul", { className: "nav nav-tabs mb-4" },
|
|
193
|
+
react_1.default.createElement("li", { className: "nav-item" },
|
|
194
|
+
react_1.default.createElement("a", { className: "nav-link", href: "#/data-explorer" },
|
|
195
|
+
react_1.default.createElement("i", { className: "bi bi-table" }),
|
|
196
|
+
" Tables")),
|
|
197
|
+
react_1.default.createElement("li", { className: "nav-item" },
|
|
198
|
+
react_1.default.createElement("a", { className: "nav-link active", href: "#/data-explorer/query" },
|
|
199
|
+
react_1.default.createElement("i", { className: "bi bi-terminal" }),
|
|
200
|
+
" Query"))),
|
|
201
|
+
react_1.default.createElement("h2", { className: "mb-3" }, "SQL Query Executor"),
|
|
202
|
+
react_1.default.createElement("div", { className: "mb-3" },
|
|
203
|
+
react_1.default.createElement("ul", { className: "nav nav-tabs" },
|
|
204
|
+
tabs.map((tab) => (react_1.default.createElement("li", { key: tab.id, className: "nav-item" },
|
|
205
|
+
react_1.default.createElement("div", { className: `nav-link ${tab.id === activeTabId ? 'active' : ''}`, style: { cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
206
|
+
react_1.default.createElement("span", { onClick: () => activeQueryTabId(tab.id), style: { flex: 1 } }, tab.name),
|
|
207
|
+
tabs.length > 1 && (react_1.default.createElement("button", { className: "btn btn-sm p-0", onClick: (e) => {
|
|
208
|
+
e.stopPropagation();
|
|
209
|
+
closeTab(tab.id);
|
|
210
|
+
}, style: {
|
|
211
|
+
border: 'none',
|
|
212
|
+
background: 'none',
|
|
213
|
+
fontSize: '1.2em',
|
|
214
|
+
lineHeight: 1,
|
|
215
|
+
opacity: 0.6
|
|
216
|
+
}, title: "Close tab" }, "\u00D7")))))),
|
|
217
|
+
react_1.default.createElement("li", { className: "nav-item" },
|
|
218
|
+
react_1.default.createElement("button", { className: "nav-link", onClick: addNewTab, style: { border: 'none', background: 'none' }, title: "New query tab" },
|
|
219
|
+
react_1.default.createElement("i", { className: "bi bi-plus-lg" }))))),
|
|
220
|
+
react_1.default.createElement("div", { className: "card mb-4" },
|
|
221
|
+
react_1.default.createElement("div", { className: "card-body" },
|
|
222
|
+
react_1.default.createElement("label", { htmlFor: "query-input", className: "form-label" },
|
|
223
|
+
react_1.default.createElement("strong", null, "SQL Query"),
|
|
224
|
+
react_1.default.createElement("span", { className: "text-muted small ms-2" }, "(Read-only: SELECT and PRAGMA only)")),
|
|
225
|
+
react_1.default.createElement("textarea", { id: "query-input", className: "form-control font-monospace", rows: 6, value: query, onChange: (e) => setQuery(e.target.value), onKeyDown: handleKeyDown, placeholder: "Enter your SQL query here..." }),
|
|
226
|
+
react_1.default.createElement("div", { className: "mt-3 d-flex justify-content-between align-items-center" },
|
|
227
|
+
react_1.default.createElement("button", { className: "btn btn-primary", onClick: executeQuery, disabled: isLoading || !query.trim() }, isLoading ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
228
|
+
react_1.default.createElement("span", { className: "spinner-border spinner-border-sm me-2", role: "status", "aria-hidden": "true" }),
|
|
229
|
+
"Executing...")) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
230
|
+
react_1.default.createElement("i", { className: "bi bi-play-fill" }),
|
|
231
|
+
" Execute Query"))),
|
|
232
|
+
react_1.default.createElement("small", { className: "text-muted" },
|
|
233
|
+
react_1.default.createElement("i", { className: "bi bi-info-circle" }),
|
|
234
|
+
" Press Cmd/Ctrl + Enter to execute")))),
|
|
235
|
+
error && (react_1.default.createElement("div", { className: "alert alert-danger", role: "alert" },
|
|
236
|
+
react_1.default.createElement("i", { className: "bi bi-exclamation-triangle-fill me-2" }),
|
|
237
|
+
react_1.default.createElement("strong", null, "Error:"),
|
|
238
|
+
" ",
|
|
239
|
+
error)),
|
|
240
|
+
isLoading && (react_1.default.createElement("div", { className: "text-center py-5" },
|
|
241
|
+
react_1.default.createElement(loading_indicator_1.LoadingIndicator, null))),
|
|
242
|
+
result && !isLoading && (react_1.default.createElement("div", { className: "card" },
|
|
243
|
+
react_1.default.createElement("div", { className: "card-body" },
|
|
244
|
+
react_1.default.createElement("div", { className: "d-flex justify-content-between align-items-center mb-3" },
|
|
245
|
+
react_1.default.createElement("h5", { className: "card-title mb-0" },
|
|
246
|
+
"Query Results",
|
|
247
|
+
react_1.default.createElement("span", { className: "badge bg-primary ms-2" },
|
|
248
|
+
result.rowCount,
|
|
249
|
+
" rows")),
|
|
250
|
+
react_1.default.createElement("button", { className: "btn btn-sm btn-outline-secondary", onClick: () => setResult(null) },
|
|
251
|
+
react_1.default.createElement("i", { className: "bi bi-x" }),
|
|
252
|
+
" Clear")),
|
|
253
|
+
result.rowCount === 0 ? (react_1.default.createElement("div", { className: "alert alert-info mb-0" }, "Query executed successfully but returned no rows.")) : (react_1.default.createElement("div", { className: "table-responsive" },
|
|
254
|
+
react_1.default.createElement("table", { className: "table table-sm table-hover table-bordered" },
|
|
255
|
+
react_1.default.createElement("thead", { className: "table-light" },
|
|
256
|
+
react_1.default.createElement("tr", null, result.columns.map((col, idx) => (react_1.default.createElement("th", { key: idx, className: "font-monospace small" }, col))))),
|
|
257
|
+
react_1.default.createElement("tbody", null, result.rows.map((row, rowIdx) => (react_1.default.createElement("tr", { key: rowIdx }, row.map((cell, cellIdx) => (react_1.default.createElement("td", { key: cellIdx, className: "font-monospace small" }, cell === null ? (react_1.default.createElement("span", { className: "text-muted fst-italic" }, "null")) : typeof cell === 'object' ? (react_1.default.createElement("pre", { className: "mb-0", style: { fontSize: '0.7rem' } }, JSON.stringify(cell, null, 2))) : (String(cell))))))))))))))),
|
|
258
|
+
!result && !isLoading && !error && (react_1.default.createElement("div", { className: "card" },
|
|
259
|
+
react_1.default.createElement("div", { className: "card-body" },
|
|
260
|
+
react_1.default.createElement("h5", { className: "card-title" }, "Example Queries"),
|
|
261
|
+
react_1.default.createElement("ul", { className: "list-unstyled mb-0" },
|
|
262
|
+
react_1.default.createElement("li", { className: "mb-2" },
|
|
263
|
+
react_1.default.createElement("button", { className: "btn btn-sm btn-outline-secondary font-monospace", onClick: () => setQuery('SELECT * FROM sqlite_master WHERE type=\'table\'') }, "List all tables")),
|
|
264
|
+
react_1.default.createElement("li", { className: "mb-2" },
|
|
265
|
+
react_1.default.createElement("button", { className: "btn btn-sm btn-outline-secondary font-monospace", onClick: () => setQuery('PRAGMA table_info(Users)') }, "Show Users table schema")),
|
|
266
|
+
react_1.default.createElement("li", { className: "mb-2" },
|
|
267
|
+
react_1.default.createElement("button", { className: "btn btn-sm btn-outline-secondary font-monospace", onClick: () => setQuery('SELECT name, sql FROM sqlite_master WHERE type=\'index\'') }, "List all indexes")),
|
|
268
|
+
react_1.default.createElement("li", { className: "mb-2" },
|
|
269
|
+
react_1.default.createElement("button", { className: "btn btn-sm btn-outline-secondary font-monospace", onClick: () => setQuery('PRAGMA database_list') }, "Show attached databases"))))))));
|
|
270
|
+
}
|
|
271
|
+
(0, ui_loader_1.registerInternalPeersUI)({
|
|
272
|
+
peersUIId: 'data-explorer-query-ui',
|
|
273
|
+
component: QueryExecutor,
|
|
274
|
+
routes: [
|
|
275
|
+
{
|
|
276
|
+
isMatch: (_props, context) => context.path === 'data-explorer/query',
|
|
277
|
+
uiCategory: 'screen',
|
|
278
|
+
priority: 2
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
});
|