dbgate-api 5.1.5 → 5.1.7-alpha.13
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/env/dblogin/.env +14 -0
- package/env/singledb/.env +2 -2
- package/package.json +10 -5
- package/src/controllers/apps.js +6 -6
- package/src/controllers/archive.js +15 -5
- package/src/controllers/auth.js +143 -0
- package/src/controllers/config.js +11 -2
- package/src/controllers/connections.js +70 -3
- package/src/controllers/databaseConnections.js +9 -5
- package/src/controllers/files.js +7 -7
- package/src/controllers/jsldata.js +92 -7
- package/src/controllers/serverConnections.js +52 -4
- package/src/controllers/sessions.js +42 -0
- package/src/currentVersion.js +2 -2
- package/src/main.js +22 -13
- package/src/nativeModulesContent.js +1 -1
- package/src/proc/databaseConnectionProcess.js +2 -2
- package/src/proc/serverConnectionProcess.js +36 -2
- package/src/proc/sessionProcess.js +45 -0
- package/src/utility/JsonLinesDatabase.js +6 -0
- package/src/utility/JsonLinesDatastore.js +11 -3
- package/src/utility/exceptions.js +9 -0
- package/src/utility/requirePluginFunction.js +16 -0
- package/src/utility/socket.js +4 -3
- package/src/utility/useController.js +17 -1
|
@@ -7,6 +7,7 @@ const DatastoreProxy = require('../utility/DatastoreProxy');
|
|
|
7
7
|
const { saveFreeTableData } = require('../utility/freeTableStorage');
|
|
8
8
|
const getJslFileName = require('../utility/getJslFileName');
|
|
9
9
|
const JsonLinesDatastore = require('../utility/JsonLinesDatastore');
|
|
10
|
+
const requirePluginFunction = require('../utility/requirePluginFunction');
|
|
10
11
|
const socket = require('../utility/socket');
|
|
11
12
|
|
|
12
13
|
function readFirstLine(file) {
|
|
@@ -99,10 +100,13 @@ module.exports = {
|
|
|
99
100
|
// return readerInfo;
|
|
100
101
|
// },
|
|
101
102
|
|
|
102
|
-
async ensureDatastore(jslid) {
|
|
103
|
+
async ensureDatastore(jslid, formatterFunction) {
|
|
103
104
|
let datastore = this.datastores[jslid];
|
|
104
|
-
if (!datastore) {
|
|
105
|
-
|
|
105
|
+
if (!datastore || datastore.formatterFunction != formatterFunction) {
|
|
106
|
+
if (datastore) {
|
|
107
|
+
datastore._closeReader();
|
|
108
|
+
}
|
|
109
|
+
datastore = new JsonLinesDatastore(getJslFileName(jslid), formatterFunction);
|
|
106
110
|
// datastore = new DatastoreProxy(getJslFileName(jslid));
|
|
107
111
|
this.datastores[jslid] = datastore;
|
|
108
112
|
}
|
|
@@ -131,8 +135,8 @@ module.exports = {
|
|
|
131
135
|
},
|
|
132
136
|
|
|
133
137
|
getRows_meta: true,
|
|
134
|
-
async getRows({ jslid, offset, limit, filters }) {
|
|
135
|
-
const datastore = await this.ensureDatastore(jslid);
|
|
138
|
+
async getRows({ jslid, offset, limit, filters, formatterFunction }) {
|
|
139
|
+
const datastore = await this.ensureDatastore(jslid, formatterFunction);
|
|
136
140
|
return datastore.getRows(offset, limit, _.isEmpty(filters) ? null : filters);
|
|
137
141
|
},
|
|
138
142
|
|
|
@@ -150,8 +154,8 @@ module.exports = {
|
|
|
150
154
|
},
|
|
151
155
|
|
|
152
156
|
loadFieldValues_meta: true,
|
|
153
|
-
async loadFieldValues({ jslid, field, search }) {
|
|
154
|
-
const datastore = await this.ensureDatastore(jslid);
|
|
157
|
+
async loadFieldValues({ jslid, field, search, formatterFunction }) {
|
|
158
|
+
const datastore = await this.ensureDatastore(jslid, formatterFunction);
|
|
155
159
|
const res = new Set();
|
|
156
160
|
await datastore.enumRows(row => {
|
|
157
161
|
if (!filterName(search, row[field])) return true;
|
|
@@ -188,4 +192,85 @@ module.exports = {
|
|
|
188
192
|
await fs.promises.writeFile(getJslFileName(jslid), text);
|
|
189
193
|
return true;
|
|
190
194
|
},
|
|
195
|
+
|
|
196
|
+
extractTimelineChart_meta: true,
|
|
197
|
+
async extractTimelineChart({ jslid, timestampFunction, aggregateFunction, measures }) {
|
|
198
|
+
const timestamp = requirePluginFunction(timestampFunction);
|
|
199
|
+
const aggregate = requirePluginFunction(aggregateFunction);
|
|
200
|
+
const datastore = new JsonLinesDatastore(getJslFileName(jslid));
|
|
201
|
+
let mints = null;
|
|
202
|
+
let maxts = null;
|
|
203
|
+
// pass 1 - counts stats, time range
|
|
204
|
+
await datastore.enumRows(row => {
|
|
205
|
+
const ts = timestamp(row);
|
|
206
|
+
if (!mints || ts < mints) mints = ts;
|
|
207
|
+
if (!maxts || ts > maxts) maxts = ts;
|
|
208
|
+
return true;
|
|
209
|
+
});
|
|
210
|
+
const minTime = new Date(mints).getTime();
|
|
211
|
+
const maxTime = new Date(maxts).getTime();
|
|
212
|
+
const duration = maxTime - minTime;
|
|
213
|
+
const STEPS = 100;
|
|
214
|
+
let stepCount = duration > 100 * 1000 ? STEPS : Math.round((maxTime - minTime) / 1000);
|
|
215
|
+
if (stepCount < 2) {
|
|
216
|
+
stepCount = 2;
|
|
217
|
+
}
|
|
218
|
+
const stepDuration = duration / stepCount;
|
|
219
|
+
const labels = _.range(stepCount).map(i => new Date(minTime + stepDuration / 2 + stepDuration * i));
|
|
220
|
+
|
|
221
|
+
// const datasets = measures.map(m => ({
|
|
222
|
+
// label: m.label,
|
|
223
|
+
// data: Array(stepCount).fill(0),
|
|
224
|
+
// }));
|
|
225
|
+
|
|
226
|
+
const mproc = measures.map(m => ({
|
|
227
|
+
...m,
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
const data = Array(stepCount)
|
|
231
|
+
.fill(0)
|
|
232
|
+
.map(() => ({}));
|
|
233
|
+
|
|
234
|
+
// pass 2 - count measures
|
|
235
|
+
await datastore.enumRows(row => {
|
|
236
|
+
const ts = timestamp(row);
|
|
237
|
+
let part = Math.round((new Date(ts).getTime() - minTime) / stepDuration);
|
|
238
|
+
if (part < 0) part = 0;
|
|
239
|
+
if (part >= stepCount) part - stepCount - 1;
|
|
240
|
+
if (data[part]) {
|
|
241
|
+
data[part] = aggregate(data[part], row, stepDuration);
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
datastore._closeReader();
|
|
247
|
+
|
|
248
|
+
// const measureByField = _.fromPairs(measures.map((m, i) => [m.field, i]));
|
|
249
|
+
|
|
250
|
+
// for (let mindex = 0; mindex < measures.length; mindex++) {
|
|
251
|
+
// for (let stepIndex = 0; stepIndex < stepCount; stepIndex++) {
|
|
252
|
+
// const measure = measures[mindex];
|
|
253
|
+
// if (measure.perSecond) {
|
|
254
|
+
// datasets[mindex].data[stepIndex] /= stepDuration / 1000;
|
|
255
|
+
// }
|
|
256
|
+
// if (measure.perField) {
|
|
257
|
+
// datasets[mindex].data[stepIndex] /= datasets[measureByField[measure.perField]].data[stepIndex];
|
|
258
|
+
// }
|
|
259
|
+
// }
|
|
260
|
+
// }
|
|
261
|
+
|
|
262
|
+
// for (let i = 0; i < measures.length; i++) {
|
|
263
|
+
// if (measures[i].hidden) {
|
|
264
|
+
// datasets[i] = null;
|
|
265
|
+
// }
|
|
266
|
+
// }
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
labels,
|
|
270
|
+
datasets: mproc.map(m => ({
|
|
271
|
+
label: m.label,
|
|
272
|
+
data: data.map(d => d[m.field] || 0),
|
|
273
|
+
})),
|
|
274
|
+
};
|
|
275
|
+
},
|
|
191
276
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const connections = require('./connections');
|
|
2
2
|
const socket = require('../utility/socket');
|
|
3
3
|
const { fork } = require('child_process');
|
|
4
|
+
const uuidv1 = require('uuid/v1');
|
|
4
5
|
const _ = require('lodash');
|
|
5
6
|
const AsyncLock = require('async-lock');
|
|
6
7
|
const { handleProcessCommunication } = require('../utility/processComm');
|
|
@@ -8,23 +9,25 @@ const lock = new AsyncLock();
|
|
|
8
9
|
const config = require('./config');
|
|
9
10
|
const processArgs = require('../utility/processArgs');
|
|
10
11
|
const { testConnectionPermission } = require('../utility/hasPermission');
|
|
12
|
+
const { MissingCredentialsError } = require('../utility/exceptions');
|
|
11
13
|
|
|
12
14
|
module.exports = {
|
|
13
15
|
opened: [],
|
|
14
16
|
closed: {},
|
|
15
17
|
lastPinged: {},
|
|
18
|
+
requests: {},
|
|
16
19
|
|
|
17
20
|
handle_databases(conid, { databases }) {
|
|
18
21
|
const existing = this.opened.find(x => x.conid == conid);
|
|
19
22
|
if (!existing) return;
|
|
20
23
|
existing.databases = databases;
|
|
21
|
-
socket.emitChanged(`database-list-changed
|
|
24
|
+
socket.emitChanged(`database-list-changed`, { conid });
|
|
22
25
|
},
|
|
23
26
|
handle_version(conid, { version }) {
|
|
24
27
|
const existing = this.opened.find(x => x.conid == conid);
|
|
25
28
|
if (!existing) return;
|
|
26
29
|
existing.version = version;
|
|
27
|
-
socket.emitChanged(`server-version-changed
|
|
30
|
+
socket.emitChanged(`server-version-changed`, { conid });
|
|
28
31
|
},
|
|
29
32
|
handle_status(conid, { status }) {
|
|
30
33
|
const existing = this.opened.find(x => x.conid == conid);
|
|
@@ -33,12 +36,20 @@ module.exports = {
|
|
|
33
36
|
socket.emitChanged(`server-status-changed`);
|
|
34
37
|
},
|
|
35
38
|
handle_ping() {},
|
|
39
|
+
handle_response(conid, { msgid, ...response }) {
|
|
40
|
+
const [resolve, reject] = this.requests[msgid];
|
|
41
|
+
resolve(response);
|
|
42
|
+
delete this.requests[msgid];
|
|
43
|
+
},
|
|
36
44
|
|
|
37
45
|
async ensureOpened(conid) {
|
|
38
46
|
const res = await lock.acquire(conid, async () => {
|
|
39
47
|
const existing = this.opened.find(x => x.conid == conid);
|
|
40
48
|
if (existing) return existing;
|
|
41
49
|
const connection = await connections.getCore({ conid });
|
|
50
|
+
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
|
51
|
+
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
|
52
|
+
}
|
|
42
53
|
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
|
43
54
|
'--is-forked-api',
|
|
44
55
|
'--start-process',
|
|
@@ -120,9 +131,9 @@ module.exports = {
|
|
|
120
131
|
},
|
|
121
132
|
|
|
122
133
|
ping_meta: true,
|
|
123
|
-
async ping({
|
|
134
|
+
async ping({ conidArray }) {
|
|
124
135
|
await Promise.all(
|
|
125
|
-
_.uniq(
|
|
136
|
+
_.uniq(conidArray).map(async conid => {
|
|
126
137
|
const last = this.lastPinged[conid];
|
|
127
138
|
if (last && new Date().getTime() - last < 30 * 1000) {
|
|
128
139
|
return Promise.resolve();
|
|
@@ -161,4 +172,41 @@ module.exports = {
|
|
|
161
172
|
opened.subprocess.send({ msgtype: 'dropDatabase', name });
|
|
162
173
|
return { status: 'ok' };
|
|
163
174
|
},
|
|
175
|
+
|
|
176
|
+
sendRequest(conn, message) {
|
|
177
|
+
const msgid = uuidv1();
|
|
178
|
+
const promise = new Promise((resolve, reject) => {
|
|
179
|
+
this.requests[msgid] = [resolve, reject];
|
|
180
|
+
conn.subprocess.send({ msgid, ...message });
|
|
181
|
+
});
|
|
182
|
+
return promise;
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
async loadDataCore(msgtype, { conid, ...args }, req) {
|
|
186
|
+
testConnectionPermission(conid, req);
|
|
187
|
+
const opened = await this.ensureOpened(conid);
|
|
188
|
+
const res = await this.sendRequest(opened, { msgtype, ...args });
|
|
189
|
+
if (res.errorMessage) {
|
|
190
|
+
console.error(res.errorMessage);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
errorMessage: res.errorMessage,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return res.result || null;
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
serverSummary_meta: true,
|
|
200
|
+
async serverSummary({ conid }, req) {
|
|
201
|
+
testConnectionPermission(conid, req);
|
|
202
|
+
return this.loadDataCore('serverSummary', { conid });
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
summaryCommand_meta: true,
|
|
206
|
+
async summaryCommand({ conid, command, row }, req) {
|
|
207
|
+
testConnectionPermission(conid, req);
|
|
208
|
+
const opened = await this.ensureOpened(conid);
|
|
209
|
+
if (opened.connection.isReadOnly) return false;
|
|
210
|
+
return this.loadDataCore('summaryCommand', { conid, command, row });
|
|
211
|
+
},
|
|
164
212
|
};
|
|
@@ -103,6 +103,12 @@ module.exports = {
|
|
|
103
103
|
if (handleProcessCommunication(message, subprocess)) return;
|
|
104
104
|
this[`handle_${msgtype}`](sesid, message);
|
|
105
105
|
});
|
|
106
|
+
subprocess.on('exit', () => {
|
|
107
|
+
this.opened = this.opened.filter(x => x.sesid != sesid);
|
|
108
|
+
this.dispatchMessage(sesid, 'Query session closed');
|
|
109
|
+
socket.emit(`session-closed-${sesid}`);
|
|
110
|
+
});
|
|
111
|
+
|
|
106
112
|
subprocess.send({ msgtype: 'connect', ...connection, database });
|
|
107
113
|
return _.pick(newOpened, ['conid', 'database', 'sesid']);
|
|
108
114
|
},
|
|
@@ -144,6 +150,31 @@ module.exports = {
|
|
|
144
150
|
return true;
|
|
145
151
|
},
|
|
146
152
|
|
|
153
|
+
startProfiler_meta: true,
|
|
154
|
+
async startProfiler({ sesid }) {
|
|
155
|
+
const jslid = uuidv1();
|
|
156
|
+
const session = this.opened.find(x => x.sesid == sesid);
|
|
157
|
+
if (!session) {
|
|
158
|
+
throw new Error('Invalid session');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`Starting profiler, sesid=${sesid}`);
|
|
162
|
+
session.loadingReader_jslid = jslid;
|
|
163
|
+
session.subprocess.send({ msgtype: 'startProfiler', jslid });
|
|
164
|
+
|
|
165
|
+
return { state: 'ok', jslid };
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
stopProfiler_meta: true,
|
|
169
|
+
async stopProfiler({ sesid }) {
|
|
170
|
+
const session = this.opened.find(x => x.sesid == sesid);
|
|
171
|
+
if (!session) {
|
|
172
|
+
throw new Error('Invalid session');
|
|
173
|
+
}
|
|
174
|
+
session.subprocess.send({ msgtype: 'stopProfiler' });
|
|
175
|
+
return { state: 'ok' };
|
|
176
|
+
},
|
|
177
|
+
|
|
147
178
|
// cancel_meta: true,
|
|
148
179
|
// async cancel({ sesid }) {
|
|
149
180
|
// const session = this.opened.find((x) => x.sesid == sesid);
|
|
@@ -165,6 +196,17 @@ module.exports = {
|
|
|
165
196
|
return { state: 'ok' };
|
|
166
197
|
},
|
|
167
198
|
|
|
199
|
+
ping_meta: true,
|
|
200
|
+
async ping({ sesid }) {
|
|
201
|
+
const session = this.opened.find(x => x.sesid == sesid);
|
|
202
|
+
if (!session) {
|
|
203
|
+
throw new Error('Invalid session');
|
|
204
|
+
}
|
|
205
|
+
session.subprocess.send({ msgtype: 'ping' });
|
|
206
|
+
|
|
207
|
+
return { state: 'ok' };
|
|
208
|
+
},
|
|
209
|
+
|
|
168
210
|
// runCommand_meta: true,
|
|
169
211
|
// async runCommand({ conid, database, sql }) {
|
|
170
212
|
// console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`);
|
package/src/currentVersion.js
CHANGED
package/src/main.js
CHANGED
|
@@ -20,6 +20,7 @@ const jsldata = require('./controllers/jsldata');
|
|
|
20
20
|
const config = require('./controllers/config');
|
|
21
21
|
const archive = require('./controllers/archive');
|
|
22
22
|
const apps = require('./controllers/apps');
|
|
23
|
+
const auth = require('./controllers/auth');
|
|
23
24
|
const uploads = require('./controllers/uploads');
|
|
24
25
|
const plugins = require('./controllers/plugins');
|
|
25
26
|
const files = require('./controllers/files');
|
|
@@ -41,7 +42,7 @@ function start() {
|
|
|
41
42
|
const server = http.createServer(app);
|
|
42
43
|
|
|
43
44
|
const logins = getLogins();
|
|
44
|
-
if (logins) {
|
|
45
|
+
if (logins && process.env.BASIC_AUTH) {
|
|
45
46
|
app.use(
|
|
46
47
|
basicAuth({
|
|
47
48
|
users: _.fromPairs(logins.map(x => [x.login, x.password])),
|
|
@@ -53,6 +54,25 @@ function start() {
|
|
|
53
54
|
|
|
54
55
|
app.use(cors());
|
|
55
56
|
|
|
57
|
+
if (platformInfo.isDocker) {
|
|
58
|
+
// server static files inside docker container
|
|
59
|
+
app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
|
|
60
|
+
} else if (platformInfo.isNpmDist) {
|
|
61
|
+
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
|
|
62
|
+
} else if (process.env.DEVWEB) {
|
|
63
|
+
console.log('__dirname', __dirname);
|
|
64
|
+
console.log(path.join(__dirname, '../../web/public/build'));
|
|
65
|
+
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
|
|
66
|
+
} else {
|
|
67
|
+
app.get(getExpressPath('/'), (req, res) => {
|
|
68
|
+
res.send('DbGate API');
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (auth.shouldAuthorizeApi()) {
|
|
73
|
+
app.use(auth.authMiddleware);
|
|
74
|
+
}
|
|
75
|
+
|
|
56
76
|
app.get(getExpressPath('/stream'), async function (req, res) {
|
|
57
77
|
res.set({
|
|
58
78
|
'Cache-Control': 'no-cache',
|
|
@@ -88,14 +108,10 @@ function start() {
|
|
|
88
108
|
app.use(getExpressPath('/runners/data'), express.static(rundir()));
|
|
89
109
|
|
|
90
110
|
if (platformInfo.isDocker) {
|
|
91
|
-
// server static files inside docker container
|
|
92
|
-
app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
|
|
93
|
-
|
|
94
111
|
const port = process.env.PORT || 3000;
|
|
95
112
|
console.log('DbGate API listening on port (docker build)', port);
|
|
96
113
|
server.listen(port);
|
|
97
114
|
} else if (platformInfo.isNpmDist) {
|
|
98
|
-
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
|
|
99
115
|
getPort({
|
|
100
116
|
port: parseInt(
|
|
101
117
|
// @ts-ignore
|
|
@@ -107,18 +123,10 @@ function start() {
|
|
|
107
123
|
});
|
|
108
124
|
});
|
|
109
125
|
} else if (process.env.DEVWEB) {
|
|
110
|
-
console.log('__dirname', __dirname);
|
|
111
|
-
console.log(path.join(__dirname, '../../web/public/build'));
|
|
112
|
-
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
|
|
113
|
-
|
|
114
126
|
const port = process.env.PORT || 3000;
|
|
115
127
|
console.log('DbGate API & web listening on port (dev web build)', port);
|
|
116
128
|
server.listen(port);
|
|
117
129
|
} else {
|
|
118
|
-
app.get(getExpressPath('/'), (req, res) => {
|
|
119
|
-
res.send('DbGate API');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
130
|
const port = process.env.PORT || 3000;
|
|
123
131
|
console.log('DbGate API listening on port (dev API build)', port);
|
|
124
132
|
server.listen(port);
|
|
@@ -157,6 +165,7 @@ function useAllControllers(app, electron) {
|
|
|
157
165
|
useController(app, electron, '/scheduler', scheduler);
|
|
158
166
|
useController(app, electron, '/query-history', queryHistory);
|
|
159
167
|
useController(app, electron, '/apps', apps);
|
|
168
|
+
useController(app, electron, '/auth', auth);
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
function setElectronSender(electronSender) {
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
|
|
3
3
|
const content = {};
|
|
4
4
|
|
|
5
|
-
content['better-sqlite3'] = () => require('better-sqlite3');
|
|
5
|
+
content.oracledb = () => require('oracledb');content['better-sqlite3'] = () => require('better-sqlite3');
|
|
6
6
|
|
|
7
7
|
module.exports = content;
|
|
@@ -335,11 +335,11 @@ function start() {
|
|
|
335
335
|
|
|
336
336
|
setInterval(() => {
|
|
337
337
|
const time = new Date().getTime();
|
|
338
|
-
if (time - lastPing >
|
|
338
|
+
if (time - lastPing > 40 * 1000) {
|
|
339
339
|
console.log('Database connection not alive, exiting');
|
|
340
340
|
process.exit(0);
|
|
341
341
|
}
|
|
342
|
-
},
|
|
342
|
+
}, 10 * 1000);
|
|
343
343
|
|
|
344
344
|
process.on('message', async message => {
|
|
345
345
|
if (handleProcessCommunication(message)) return;
|
|
@@ -10,6 +10,7 @@ let storedConnection;
|
|
|
10
10
|
let lastDatabases = null;
|
|
11
11
|
let lastStatus = null;
|
|
12
12
|
let lastPing = null;
|
|
13
|
+
let afterConnectCallbacks = [];
|
|
13
14
|
|
|
14
15
|
async function handleRefresh() {
|
|
15
16
|
const driver = requireEngineDriver(storedConnection);
|
|
@@ -74,6 +75,18 @@ async function handleConnect(connection) {
|
|
|
74
75
|
// console.error(err);
|
|
75
76
|
setTimeout(() => process.exit(1), 1000);
|
|
76
77
|
}
|
|
78
|
+
|
|
79
|
+
for (const [resolve] of afterConnectCallbacks) {
|
|
80
|
+
resolve();
|
|
81
|
+
}
|
|
82
|
+
afterConnectCallbacks = [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function waitConnected() {
|
|
86
|
+
if (systemConnection) return Promise.resolve();
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
afterConnectCallbacks.push([resolve, reject]);
|
|
89
|
+
});
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
function handlePing() {
|
|
@@ -94,9 +107,30 @@ async function handleDatabaseOp(op, { name }) {
|
|
|
94
107
|
await handleRefresh();
|
|
95
108
|
}
|
|
96
109
|
|
|
110
|
+
async function handleDriverDataCore(msgid, callMethod) {
|
|
111
|
+
await waitConnected();
|
|
112
|
+
const driver = requireEngineDriver(storedConnection);
|
|
113
|
+
try {
|
|
114
|
+
const result = await callMethod(driver);
|
|
115
|
+
process.send({ msgtype: 'response', msgid, result });
|
|
116
|
+
} catch (err) {
|
|
117
|
+
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function handleServerSummary({ msgid }) {
|
|
122
|
+
return handleDriverDataCore(msgid, driver => driver.serverSummary(systemConnection));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function handleSummaryCommand({ msgid, command, row }) {
|
|
126
|
+
return handleDriverDataCore(msgid, driver => driver.summaryCommand(systemConnection, command, row));
|
|
127
|
+
}
|
|
128
|
+
|
|
97
129
|
const messageHandlers = {
|
|
98
130
|
connect: handleConnect,
|
|
99
131
|
ping: handlePing,
|
|
132
|
+
serverSummary: handleServerSummary,
|
|
133
|
+
summaryCommand: handleSummaryCommand,
|
|
100
134
|
createDatabase: props => handleDatabaseOp('createDatabase', props),
|
|
101
135
|
dropDatabase: props => handleDatabaseOp('dropDatabase', props),
|
|
102
136
|
};
|
|
@@ -111,11 +145,11 @@ function start() {
|
|
|
111
145
|
|
|
112
146
|
setInterval(() => {
|
|
113
147
|
const time = new Date().getTime();
|
|
114
|
-
if (time - lastPing >
|
|
148
|
+
if (time - lastPing > 40 * 1000) {
|
|
115
149
|
console.log('Server connection not alive, exiting');
|
|
116
150
|
process.exit(0);
|
|
117
151
|
}
|
|
118
|
-
},
|
|
152
|
+
}, 10 * 1000);
|
|
119
153
|
|
|
120
154
|
process.on('message', async message => {
|
|
121
155
|
if (handleProcessCommunication(message)) return;
|
|
@@ -15,6 +15,8 @@ let systemConnection;
|
|
|
15
15
|
let storedConnection;
|
|
16
16
|
let afterConnectCallbacks = [];
|
|
17
17
|
// let currentHandlers = [];
|
|
18
|
+
let lastPing = null;
|
|
19
|
+
let currentProfiler = null;
|
|
18
20
|
|
|
19
21
|
class TableWriter {
|
|
20
22
|
constructor() {
|
|
@@ -209,6 +211,31 @@ function waitConnected() {
|
|
|
209
211
|
});
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
async function handleStartProfiler({ jslid }) {
|
|
215
|
+
await waitConnected();
|
|
216
|
+
const driver = requireEngineDriver(storedConnection);
|
|
217
|
+
|
|
218
|
+
if (!allowExecuteCustomScript(driver)) {
|
|
219
|
+
process.send({ msgtype: 'done' });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const writer = new TableWriter();
|
|
224
|
+
writer.initializeFromReader(jslid);
|
|
225
|
+
|
|
226
|
+
currentProfiler = await driver.startProfiler(systemConnection, {
|
|
227
|
+
row: data => writer.rowFromReader(data),
|
|
228
|
+
});
|
|
229
|
+
currentProfiler.writer = writer;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function handleStopProfiler({ jslid }) {
|
|
233
|
+
const driver = requireEngineDriver(storedConnection);
|
|
234
|
+
currentProfiler.writer.close();
|
|
235
|
+
driver.stopProfiler(systemConnection, currentProfiler);
|
|
236
|
+
currentProfiler = null;
|
|
237
|
+
}
|
|
238
|
+
|
|
212
239
|
async function handleExecuteQuery({ sql }) {
|
|
213
240
|
await waitConnected();
|
|
214
241
|
const driver = requireEngineDriver(storedConnection);
|
|
@@ -271,10 +298,17 @@ async function handleExecuteReader({ jslid, sql, fileName }) {
|
|
|
271
298
|
});
|
|
272
299
|
}
|
|
273
300
|
|
|
301
|
+
function handlePing() {
|
|
302
|
+
lastPing = new Date().getTime();
|
|
303
|
+
}
|
|
304
|
+
|
|
274
305
|
const messageHandlers = {
|
|
275
306
|
connect: handleConnect,
|
|
276
307
|
executeQuery: handleExecuteQuery,
|
|
277
308
|
executeReader: handleExecuteReader,
|
|
309
|
+
startProfiler: handleStartProfiler,
|
|
310
|
+
stopProfiler: handleStopProfiler,
|
|
311
|
+
ping: handlePing,
|
|
278
312
|
// cancel: handleCancel,
|
|
279
313
|
};
|
|
280
314
|
|
|
@@ -285,6 +319,17 @@ async function handleMessage({ msgtype, ...other }) {
|
|
|
285
319
|
|
|
286
320
|
function start() {
|
|
287
321
|
childProcessChecker();
|
|
322
|
+
|
|
323
|
+
lastPing = new Date().getTime();
|
|
324
|
+
|
|
325
|
+
setInterval(() => {
|
|
326
|
+
const time = new Date().getTime();
|
|
327
|
+
if (time - lastPing > 25 * 1000) {
|
|
328
|
+
console.log('Session not alive, exiting');
|
|
329
|
+
process.exit(0);
|
|
330
|
+
}
|
|
331
|
+
}, 10 * 1000);
|
|
332
|
+
|
|
288
333
|
process.on('message', async message => {
|
|
289
334
|
if (handleProcessCommunication(message)) return;
|
|
290
335
|
try {
|
|
@@ -90,6 +90,12 @@ class JsonLinesDatabase {
|
|
|
90
90
|
return obj;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
async updateAll(mapFunction) {
|
|
94
|
+
await this._ensureLoaded();
|
|
95
|
+
this.data = this.data.map(mapFunction);
|
|
96
|
+
await this._save();
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
async patch(id, values) {
|
|
94
100
|
await this._ensureLoaded();
|
|
95
101
|
this.data = this.data.map(x => (x._id == id ? { ...x, ...values } : x));
|
|
@@ -3,6 +3,7 @@ const AsyncLock = require('async-lock');
|
|
|
3
3
|
const lock = new AsyncLock();
|
|
4
4
|
const stableStringify = require('json-stable-stringify');
|
|
5
5
|
const { evaluateCondition } = require('dbgate-sqltree');
|
|
6
|
+
const requirePluginFunction = require('./requirePluginFunction');
|
|
6
7
|
|
|
7
8
|
function fetchNextLineFromReader(reader) {
|
|
8
9
|
return new Promise((resolve, reject) => {
|
|
@@ -22,14 +23,16 @@ function fetchNextLineFromReader(reader) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
class JsonLinesDatastore {
|
|
25
|
-
constructor(file) {
|
|
26
|
+
constructor(file, formatterFunction) {
|
|
26
27
|
this.file = file;
|
|
28
|
+
this.formatterFunction = formatterFunction;
|
|
27
29
|
this.reader = null;
|
|
28
30
|
this.readedDataRowCount = 0;
|
|
29
31
|
this.readedSchemaRow = false;
|
|
30
32
|
// this.firstRowToBeReturned = null;
|
|
31
33
|
this.notifyChangedCallback = null;
|
|
32
34
|
this.currentFilter = null;
|
|
35
|
+
this.rowFormatter = requirePluginFunction(formatterFunction);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
_closeReader() {
|
|
@@ -62,6 +65,11 @@ class JsonLinesDatastore {
|
|
|
62
65
|
);
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
parseLine(line) {
|
|
69
|
+
const res = JSON.parse(line);
|
|
70
|
+
return this.rowFormatter ? this.rowFormatter(res) : res;
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
async _readLine(parse) {
|
|
66
74
|
// if (this.firstRowToBeReturned) {
|
|
67
75
|
// const res = this.firstRowToBeReturned;
|
|
@@ -84,14 +92,14 @@ class JsonLinesDatastore {
|
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
if (this.currentFilter) {
|
|
87
|
-
const parsedLine =
|
|
95
|
+
const parsedLine = this.parseLine(line);
|
|
88
96
|
if (evaluateCondition(this.currentFilter, parsedLine)) {
|
|
89
97
|
this.readedDataRowCount += 1;
|
|
90
98
|
return parse ? parsedLine : true;
|
|
91
99
|
}
|
|
92
100
|
} else {
|
|
93
101
|
this.readedDataRowCount += 1;
|
|
94
|
-
return parse ?
|
|
102
|
+
return parse ? this.parseLine(line) : true;
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const requirePlugin = require('../shell/requirePlugin');
|
|
3
|
+
|
|
4
|
+
function requirePluginFunction(functionName) {
|
|
5
|
+
if (!functionName) return null;
|
|
6
|
+
if (functionName.includes('@')) {
|
|
7
|
+
const [shortName, packageName] = functionName.split('@');
|
|
8
|
+
const plugin = requirePlugin(packageName);
|
|
9
|
+
if (plugin.functions) {
|
|
10
|
+
return plugin.functions[shortName];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = requirePluginFunction;
|
package/src/utility/socket.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
+
const stableStringify = require('json-stable-stringify');
|
|
2
3
|
|
|
3
4
|
const sseResponses = [];
|
|
4
5
|
let electronSender = null;
|
|
@@ -27,12 +28,12 @@ module.exports = {
|
|
|
27
28
|
electronSender.send(message, data == null ? null : data);
|
|
28
29
|
}
|
|
29
30
|
for (const res of sseResponses) {
|
|
30
|
-
res.write(`event: ${message}\ndata: ${
|
|
31
|
+
res.write(`event: ${message}\ndata: ${stableStringify(data == null ? null : data)}\n\n`);
|
|
31
32
|
}
|
|
32
33
|
},
|
|
33
|
-
emitChanged(key) {
|
|
34
|
+
emitChanged(key, params = undefined) {
|
|
34
35
|
// console.log('EMIT CHANGED', key);
|
|
35
|
-
this.emit('changed-cache', key);
|
|
36
|
+
this.emit('changed-cache', { key, ...params });
|
|
36
37
|
// this.emit(key);
|
|
37
38
|
},
|
|
38
39
|
};
|