dbgate-api 5.1.6 → 5.1.7-alpha.14
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 +69 -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 +25 -0
- package/src/currentVersion.js +2 -2
- package/src/main.js +22 -13
- package/src/nativeModulesContent.js +1 -1
- package/src/proc/serverConnectionProcess.js +34 -0
- package/src/proc/sessionProcess.js +28 -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
package/env/dblogin/.env
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
DEVMODE=1
|
|
2
|
+
|
|
3
|
+
CONNECTIONS=mysql
|
|
4
|
+
SINGLE_CONNECTION=mysql
|
|
5
|
+
# SINGLE_DATABASE=Chinook
|
|
6
|
+
|
|
7
|
+
LABEL_mysql=MySql localhost
|
|
8
|
+
SERVER_mysql=localhost
|
|
9
|
+
# USER_mysql=root
|
|
10
|
+
PORT_mysql=3306
|
|
11
|
+
# PASSWORD_mysql=Pwd2020Db
|
|
12
|
+
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
|
13
|
+
# PASSWORD_MODE_mysql=askPassword
|
|
14
|
+
PASSWORD_MODE_mysql=askUser
|
package/env/singledb/.env
CHANGED
|
@@ -5,8 +5,8 @@ CONNECTIONS=mysql
|
|
|
5
5
|
LABEL_mysql=MySql localhost
|
|
6
6
|
SERVER_mysql=localhost
|
|
7
7
|
USER_mysql=root
|
|
8
|
-
PASSWORD_mysql=
|
|
9
|
-
PORT_mysql=
|
|
8
|
+
PASSWORD_mysql=Pwd2020Db
|
|
9
|
+
PORT_mysql=3306
|
|
10
10
|
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
|
11
11
|
DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
|
|
12
12
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbgate-api",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "5.1.
|
|
4
|
+
"version": "5.1.7-alpha.14",
|
|
5
5
|
"homepage": "https://dbgate.org/",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"dbgate"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"activedirectory2": "^2.1.0",
|
|
20
21
|
"async-lock": "^1.2.4",
|
|
21
22
|
"axios": "^0.21.1",
|
|
22
23
|
"body-parser": "^1.19.0",
|
|
@@ -25,9 +26,9 @@
|
|
|
25
26
|
"compare-versions": "^3.6.0",
|
|
26
27
|
"cors": "^2.8.5",
|
|
27
28
|
"cross-env": "^6.0.3",
|
|
28
|
-
"dbgate-query-splitter": "^4.9.
|
|
29
|
-
"dbgate-sqltree": "^5.1.
|
|
30
|
-
"dbgate-tools": "^5.1.
|
|
29
|
+
"dbgate-query-splitter": "^4.9.3",
|
|
30
|
+
"dbgate-sqltree": "^5.1.7-alpha.14",
|
|
31
|
+
"dbgate-tools": "^5.1.7-alpha.14",
|
|
31
32
|
"debug": "^4.3.4",
|
|
32
33
|
"diff": "^5.0.0",
|
|
33
34
|
"diff2html": "^3.4.13",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"is-electron": "^2.2.1",
|
|
43
44
|
"js-yaml": "^4.1.0",
|
|
44
45
|
"json-stable-stringify": "^1.0.1",
|
|
46
|
+
"jsonwebtoken": "^8.5.1",
|
|
45
47
|
"line-reader": "^0.4.0",
|
|
46
48
|
"lodash": "^4.17.21",
|
|
47
49
|
"ncp": "^2.0.0",
|
|
@@ -57,6 +59,8 @@
|
|
|
57
59
|
"start": "env-cmd node src/index.js --listen-api",
|
|
58
60
|
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
|
|
59
61
|
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
|
|
62
|
+
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
|
63
|
+
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
|
|
60
64
|
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
|
61
65
|
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
|
62
66
|
"ts": "tsc",
|
|
@@ -65,7 +69,7 @@
|
|
|
65
69
|
"devDependencies": {
|
|
66
70
|
"@types/fs-extra": "^9.0.11",
|
|
67
71
|
"@types/lodash": "^4.14.149",
|
|
68
|
-
"dbgate-types": "^5.1.
|
|
72
|
+
"dbgate-types": "^5.1.7-alpha.14",
|
|
69
73
|
"env-cmd": "^10.1.0",
|
|
70
74
|
"node-loader": "^1.0.2",
|
|
71
75
|
"nodemon": "^2.0.2",
|
|
@@ -75,6 +79,7 @@
|
|
|
75
79
|
},
|
|
76
80
|
"optionalDependencies": {
|
|
77
81
|
"better-sqlite3": "7.6.2",
|
|
82
|
+
"oracledb": "^5.5.0",
|
|
78
83
|
"msnodesqlv8": "^2.6.0"
|
|
79
84
|
}
|
|
80
85
|
}
|
package/src/controllers/apps.js
CHANGED
|
@@ -58,7 +58,7 @@ module.exports = {
|
|
|
58
58
|
|
|
59
59
|
refreshFiles_meta: true,
|
|
60
60
|
async refreshFiles({ folder }) {
|
|
61
|
-
socket.emitChanged(
|
|
61
|
+
socket.emitChanged('app-files-changed', { app: folder });
|
|
62
62
|
},
|
|
63
63
|
|
|
64
64
|
refreshFolders_meta: true,
|
|
@@ -69,7 +69,7 @@ module.exports = {
|
|
|
69
69
|
deleteFile_meta: true,
|
|
70
70
|
async deleteFile({ folder, file, fileType }) {
|
|
71
71
|
await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`));
|
|
72
|
-
socket.emitChanged(
|
|
72
|
+
socket.emitChanged('app-files-changed', { app: folder });
|
|
73
73
|
this.emitChangedDbApp(folder);
|
|
74
74
|
},
|
|
75
75
|
|
|
@@ -79,7 +79,7 @@ module.exports = {
|
|
|
79
79
|
path.join(path.join(appdir(), folder), `${file}.${fileType}`),
|
|
80
80
|
path.join(path.join(appdir(), folder), `${newFile}.${fileType}`)
|
|
81
81
|
);
|
|
82
|
-
socket.emitChanged(
|
|
82
|
+
socket.emitChanged('app-files-changed', { app: folder });
|
|
83
83
|
this.emitChangedDbApp(folder);
|
|
84
84
|
},
|
|
85
85
|
|
|
@@ -95,7 +95,7 @@ module.exports = {
|
|
|
95
95
|
if (!folder) throw new Error('Missing folder parameter');
|
|
96
96
|
await fs.rmdir(path.join(appdir(), folder), { recursive: true });
|
|
97
97
|
socket.emitChanged(`app-folders-changed`);
|
|
98
|
-
socket.emitChanged(
|
|
98
|
+
socket.emitChanged('app-files-changed', { app: folder });
|
|
99
99
|
socket.emitChanged('used-apps-changed');
|
|
100
100
|
},
|
|
101
101
|
|
|
@@ -219,7 +219,7 @@ module.exports = {
|
|
|
219
219
|
|
|
220
220
|
await fs.writeFile(file, JSON.stringify(json, undefined, 2));
|
|
221
221
|
|
|
222
|
-
socket.emitChanged(
|
|
222
|
+
socket.emitChanged('app-files-changed', { app: appFolder });
|
|
223
223
|
socket.emitChanged('used-apps-changed');
|
|
224
224
|
},
|
|
225
225
|
|
|
@@ -271,7 +271,7 @@ module.exports = {
|
|
|
271
271
|
const file = path.join(appdir(), appFolder, fileName);
|
|
272
272
|
if (!(await fs.exists(file))) {
|
|
273
273
|
await fs.writeFile(file, JSON.stringify(content, undefined, 2));
|
|
274
|
-
socket.emitChanged(
|
|
274
|
+
socket.emitChanged('app-files-changed', { app: appFolder });
|
|
275
275
|
socket.emitChanged('used-apps-changed');
|
|
276
276
|
return true;
|
|
277
277
|
}
|
|
@@ -5,6 +5,7 @@ const { archivedir, clearArchiveLinksCache, resolveArchiveFolder } = require('..
|
|
|
5
5
|
const socket = require('../utility/socket');
|
|
6
6
|
const { saveFreeTableData } = require('../utility/freeTableStorage');
|
|
7
7
|
const loadFilesRecursive = require('../utility/loadFilesRecursive');
|
|
8
|
+
const getJslFileName = require('../utility/getJslFileName');
|
|
8
9
|
|
|
9
10
|
module.exports = {
|
|
10
11
|
folders_meta: true,
|
|
@@ -74,7 +75,7 @@ module.exports = {
|
|
|
74
75
|
|
|
75
76
|
refreshFiles_meta: true,
|
|
76
77
|
async refreshFiles({ folder }) {
|
|
77
|
-
socket.emitChanged(
|
|
78
|
+
socket.emitChanged('archive-files-changed', { folder });
|
|
78
79
|
},
|
|
79
80
|
|
|
80
81
|
refreshFolders_meta: true,
|
|
@@ -85,7 +86,7 @@ module.exports = {
|
|
|
85
86
|
deleteFile_meta: true,
|
|
86
87
|
async deleteFile({ folder, file, fileType }) {
|
|
87
88
|
await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
|
|
88
|
-
socket.emitChanged(`archive-files-changed
|
|
89
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
89
90
|
},
|
|
90
91
|
|
|
91
92
|
renameFile_meta: true,
|
|
@@ -94,7 +95,7 @@ module.exports = {
|
|
|
94
95
|
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
|
|
95
96
|
path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
|
|
96
97
|
);
|
|
97
|
-
socket.emitChanged(`archive-files-changed
|
|
98
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
98
99
|
},
|
|
99
100
|
|
|
100
101
|
renameFolder_meta: true,
|
|
@@ -118,7 +119,7 @@ module.exports = {
|
|
|
118
119
|
saveFreeTable_meta: true,
|
|
119
120
|
async saveFreeTable({ folder, file, data }) {
|
|
120
121
|
await saveFreeTableData(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), data);
|
|
121
|
-
socket.emitChanged(`archive-files-changed
|
|
122
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
122
123
|
return true;
|
|
123
124
|
},
|
|
124
125
|
|
|
@@ -146,7 +147,16 @@ module.exports = {
|
|
|
146
147
|
saveText_meta: true,
|
|
147
148
|
async saveText({ folder, file, text }) {
|
|
148
149
|
await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text);
|
|
149
|
-
socket.emitChanged(`archive-files-changed
|
|
150
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
151
|
+
return true;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
saveJslData_meta: true,
|
|
155
|
+
async saveJslData({ folder, file, jslid }) {
|
|
156
|
+
const source = getJslFileName(jslid);
|
|
157
|
+
const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
|
158
|
+
await fs.copyFile(source, target);
|
|
159
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
150
160
|
return true;
|
|
151
161
|
},
|
|
152
162
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const jwt = require('jsonwebtoken');
|
|
3
|
+
const getExpressPath = require('../utility/getExpressPath');
|
|
4
|
+
const uuidv1 = require('uuid/v1');
|
|
5
|
+
const { getLogins } = require('../utility/hasPermission');
|
|
6
|
+
const AD = require('activedirectory2').promiseWrapper;
|
|
7
|
+
|
|
8
|
+
const tokenSecret = uuidv1();
|
|
9
|
+
|
|
10
|
+
function shouldAuthorizeApi() {
|
|
11
|
+
const logins = getLogins();
|
|
12
|
+
return !!process.env.OAUTH_AUTH || !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getTokenLifetime() {
|
|
16
|
+
return process.env.TOKEN_LIFETIME || '1d';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function unauthorizedResponse(req, res, text) {
|
|
20
|
+
// if (req.path == getExpressPath('/config/get-settings')) {
|
|
21
|
+
// return res.json({});
|
|
22
|
+
// }
|
|
23
|
+
// if (req.path == getExpressPath('/connections/list')) {
|
|
24
|
+
// return res.json([]);
|
|
25
|
+
// }
|
|
26
|
+
return res.sendStatus(401).send(text);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function authMiddleware(req, res, next) {
|
|
30
|
+
const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', '/auth/login', '/stream'];
|
|
31
|
+
|
|
32
|
+
if (!shouldAuthorizeApi()) {
|
|
33
|
+
return next();
|
|
34
|
+
}
|
|
35
|
+
let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x));
|
|
36
|
+
|
|
37
|
+
const authHeader = req.headers.authorization;
|
|
38
|
+
if (!authHeader) {
|
|
39
|
+
if (skipAuth) {
|
|
40
|
+
return next();
|
|
41
|
+
}
|
|
42
|
+
return unauthorizedResponse(req, res, 'missing authorization header');
|
|
43
|
+
}
|
|
44
|
+
const token = authHeader.split(' ')[1];
|
|
45
|
+
try {
|
|
46
|
+
const decoded = jwt.verify(token, tokenSecret);
|
|
47
|
+
req.user = decoded;
|
|
48
|
+
return next();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (skipAuth) {
|
|
51
|
+
return next();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log('Sending invalid token error', err.message);
|
|
55
|
+
|
|
56
|
+
return unauthorizedResponse(req, res, 'invalid token');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
oauthToken_meta: true,
|
|
62
|
+
async oauthToken(params) {
|
|
63
|
+
const { redirectUri, code } = params;
|
|
64
|
+
|
|
65
|
+
const resp = await axios.default.post(
|
|
66
|
+
`${process.env.OAUTH_TOKEN}`,
|
|
67
|
+
`grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent(
|
|
68
|
+
redirectUri
|
|
69
|
+
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}`
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const { access_token, refresh_token } = resp.data;
|
|
73
|
+
|
|
74
|
+
const payload = jwt.decode(access_token);
|
|
75
|
+
|
|
76
|
+
console.log('User payload returned from OAUTH:', payload);
|
|
77
|
+
|
|
78
|
+
const login = process.env.OAUTH_LOGIN_FIELD ? payload[process.env.OAUTH_LOGIN_FIELD] : 'oauth';
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
process.env.OAUTH_ALLOWED_LOGINS &&
|
|
82
|
+
!process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
|
83
|
+
) {
|
|
84
|
+
return { error: `Username ${login} not allowed to log in` };
|
|
85
|
+
}
|
|
86
|
+
if (access_token) {
|
|
87
|
+
return {
|
|
88
|
+
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { error: 'Token not found' };
|
|
93
|
+
},
|
|
94
|
+
login_meta: true,
|
|
95
|
+
async login(params) {
|
|
96
|
+
const { login, password } = params;
|
|
97
|
+
|
|
98
|
+
if (process.env.AD_URL) {
|
|
99
|
+
const adConfig = {
|
|
100
|
+
url: process.env.AD_URL,
|
|
101
|
+
baseDN: process.env.AD_BASEDN,
|
|
102
|
+
username: process.env.AD_USERNAME,
|
|
103
|
+
password: process.env.AD_PASSOWRD,
|
|
104
|
+
};
|
|
105
|
+
const ad = new AD(adConfig);
|
|
106
|
+
try {
|
|
107
|
+
const res = await ad.authenticate(login, password);
|
|
108
|
+
if (!res) {
|
|
109
|
+
return { error: 'Login failed' };
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
process.env.AD_ALLOWED_LOGINS &&
|
|
113
|
+
!process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
|
114
|
+
) {
|
|
115
|
+
return { error: `Username ${login} not allowed to log in` };
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
|
119
|
+
};
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.log('Failed active directory authentization', err.message);
|
|
122
|
+
return {
|
|
123
|
+
error: err.message,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const logins = getLogins();
|
|
129
|
+
if (!logins) {
|
|
130
|
+
return { error: 'Logins not configured' };
|
|
131
|
+
}
|
|
132
|
+
const foundLogin = logins.find(x => x.login == login)
|
|
133
|
+
if (foundLogin && foundLogin.password == password) {
|
|
134
|
+
return {
|
|
135
|
+
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { error: 'Invalid credentials' };
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
authMiddleware,
|
|
142
|
+
shouldAuthorizeApi,
|
|
143
|
+
};
|
|
@@ -28,18 +28,27 @@ module.exports = {
|
|
|
28
28
|
get_meta: true,
|
|
29
29
|
async get(_params, req) {
|
|
30
30
|
const logins = getLogins();
|
|
31
|
-
const login =
|
|
31
|
+
const login =
|
|
32
|
+
req && req.user
|
|
33
|
+
? req.user.login
|
|
34
|
+
: logins
|
|
35
|
+
? logins.find(x => x.login == (req && req.auth && req.auth.user))
|
|
36
|
+
: null;
|
|
32
37
|
const permissions = login ? login.permissions : process.env.PERMISSIONS;
|
|
33
38
|
|
|
34
39
|
return {
|
|
35
40
|
runAsPortal: !!connections.portalConnections,
|
|
36
|
-
|
|
41
|
+
singleDbConnection: connections.singleDbConnection,
|
|
42
|
+
singleConnection: connections.singleConnection,
|
|
37
43
|
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
|
38
44
|
allowShellConnection: platformInfo.allowShellConnection,
|
|
39
45
|
allowShellScripting: platformInfo.allowShellScripting,
|
|
40
46
|
isDocker: platformInfo.isDocker,
|
|
41
47
|
permissions,
|
|
42
48
|
login,
|
|
49
|
+
oauth: process.env.OAUTH_AUTH,
|
|
50
|
+
oauthLogout: process.env.OAUTH_LOGOUT,
|
|
51
|
+
isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH),
|
|
43
52
|
...currentVersion,
|
|
44
53
|
};
|
|
45
54
|
},
|
|
@@ -2,6 +2,7 @@ const path = require('path');
|
|
|
2
2
|
const { fork } = require('child_process');
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
|
+
const crypto = require('crypto');
|
|
5
6
|
|
|
6
7
|
const { datadir, filesdir } = require('../utility/directories');
|
|
7
8
|
const socket = require('../utility/socket');
|
|
@@ -15,6 +16,8 @@ const { safeJsonParse } = require('dbgate-tools');
|
|
|
15
16
|
const platformInfo = require('../utility/platformInfo');
|
|
16
17
|
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
|
17
18
|
|
|
19
|
+
let volatileConnections = {};
|
|
20
|
+
|
|
18
21
|
function getNamedArgs() {
|
|
19
22
|
const res = {};
|
|
20
23
|
for (let i = 0; i < process.argv.length; i++) {
|
|
@@ -49,6 +52,7 @@ function getPortalCollections() {
|
|
|
49
52
|
server: process.env[`SERVER_${id}`],
|
|
50
53
|
user: process.env[`USER_${id}`],
|
|
51
54
|
password: process.env[`PASSWORD_${id}`],
|
|
55
|
+
passwordMode: process.env[`PASSWORD_MODE_${id}`],
|
|
52
56
|
port: process.env[`PORT_${id}`],
|
|
53
57
|
databaseUrl: process.env[`URL_${id}`],
|
|
54
58
|
useDatabaseUrl: !!process.env[`URL_${id}`],
|
|
@@ -126,9 +130,10 @@ function getPortalCollections() {
|
|
|
126
130
|
|
|
127
131
|
return null;
|
|
128
132
|
}
|
|
133
|
+
|
|
129
134
|
const portalConnections = getPortalCollections();
|
|
130
135
|
|
|
131
|
-
function
|
|
136
|
+
function getSingleDbConnection() {
|
|
132
137
|
if (process.env.SINGLE_CONNECTION && process.env.SINGLE_DATABASE) {
|
|
133
138
|
// @ts-ignore
|
|
134
139
|
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
|
|
@@ -152,12 +157,31 @@ function getSingleDatabase() {
|
|
|
152
157
|
return null;
|
|
153
158
|
}
|
|
154
159
|
|
|
155
|
-
|
|
160
|
+
function getSingleConnection() {
|
|
161
|
+
if (getSingleDbConnection()) return null;
|
|
162
|
+
if (process.env.SINGLE_CONNECTION) {
|
|
163
|
+
// @ts-ignore
|
|
164
|
+
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
|
|
165
|
+
if (connection) {
|
|
166
|
+
return connection;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
const arg0 = (portalConnections || []).find(x => x._id == 'argv');
|
|
171
|
+
if (arg0) {
|
|
172
|
+
return arg0;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const singleDbConnection = getSingleDbConnection();
|
|
178
|
+
const singleConnection = getSingleConnection();
|
|
156
179
|
|
|
157
180
|
module.exports = {
|
|
158
181
|
datastore: null,
|
|
159
182
|
opened: [],
|
|
160
|
-
|
|
183
|
+
singleDbConnection,
|
|
184
|
+
singleConnection,
|
|
161
185
|
portalConnections,
|
|
162
186
|
|
|
163
187
|
async _init() {
|
|
@@ -199,6 +223,36 @@ module.exports = {
|
|
|
199
223
|
});
|
|
200
224
|
},
|
|
201
225
|
|
|
226
|
+
saveVolatile_meta: true,
|
|
227
|
+
async saveVolatile({ conid, user, password, test }) {
|
|
228
|
+
const old = await this.getCore({ conid });
|
|
229
|
+
const res = {
|
|
230
|
+
...old,
|
|
231
|
+
_id: crypto.randomUUID(),
|
|
232
|
+
password,
|
|
233
|
+
passwordMode: undefined,
|
|
234
|
+
unsaved: true,
|
|
235
|
+
};
|
|
236
|
+
if (old.passwordMode == 'askUser') {
|
|
237
|
+
res.user = user;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (test) {
|
|
241
|
+
const testRes = await this.test(res);
|
|
242
|
+
if (testRes.msgtype == 'connected') {
|
|
243
|
+
volatileConnections[res._id] = res;
|
|
244
|
+
return {
|
|
245
|
+
...res,
|
|
246
|
+
msgtype: 'connected',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return testRes;
|
|
250
|
+
} else {
|
|
251
|
+
volatileConnections[res._id] = res;
|
|
252
|
+
return res;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
|
|
202
256
|
save_meta: true,
|
|
203
257
|
async save(connection) {
|
|
204
258
|
if (portalConnections) return;
|
|
@@ -229,6 +283,14 @@ module.exports = {
|
|
|
229
283
|
return res;
|
|
230
284
|
},
|
|
231
285
|
|
|
286
|
+
batchChangeFolder_meta: true,
|
|
287
|
+
async batchChangeFolder({ folder, newFolder }, req) {
|
|
288
|
+
// const updated = await this.datastore.find(x => x.parent == folder);
|
|
289
|
+
const res = await this.datastore.updateAll(x => (x.parent == folder ? { ...x, parent: newFolder } : x));
|
|
290
|
+
socket.emitChanged('connection-list-changed');
|
|
291
|
+
return res;
|
|
292
|
+
},
|
|
293
|
+
|
|
232
294
|
updateDatabase_meta: true,
|
|
233
295
|
async updateDatabase({ conid, database, values }, req) {
|
|
234
296
|
if (portalConnections) return;
|
|
@@ -258,6 +320,10 @@ module.exports = {
|
|
|
258
320
|
|
|
259
321
|
async getCore({ conid, mask = false }) {
|
|
260
322
|
if (!conid) return null;
|
|
323
|
+
const volatile = volatileConnections[conid];
|
|
324
|
+
if (volatile) {
|
|
325
|
+
return volatile;
|
|
326
|
+
}
|
|
261
327
|
if (portalConnections) {
|
|
262
328
|
const res = portalConnections.find(x => x._id == conid) || null;
|
|
263
329
|
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
|
@@ -27,6 +27,7 @@ const { createTwoFilesPatch } = require('diff');
|
|
|
27
27
|
const diff2htmlPage = require('../utility/diff2htmlPage');
|
|
28
28
|
const processArgs = require('../utility/processArgs');
|
|
29
29
|
const { testConnectionPermission } = require('../utility/hasPermission');
|
|
30
|
+
const { MissingCredentialsError } = require('../utility/exceptions');
|
|
30
31
|
|
|
31
32
|
module.exports = {
|
|
32
33
|
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
|
@@ -42,19 +43,19 @@ module.exports = {
|
|
|
42
43
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
43
44
|
if (!existing) return;
|
|
44
45
|
existing.structure = structure;
|
|
45
|
-
socket.emitChanged(
|
|
46
|
+
socket.emitChanged('database-structure-changed', { conid, database });
|
|
46
47
|
},
|
|
47
48
|
handle_structureTime(conid, database, { analysedTime }) {
|
|
48
49
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
49
50
|
if (!existing) return;
|
|
50
51
|
existing.analysedTime = analysedTime;
|
|
51
|
-
socket.emitChanged(`database-status-changed
|
|
52
|
+
socket.emitChanged(`database-status-changed`, { conid, database });
|
|
52
53
|
},
|
|
53
54
|
handle_version(conid, database, { version }) {
|
|
54
55
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
55
56
|
if (!existing) return;
|
|
56
57
|
existing.serverVersion = version;
|
|
57
|
-
socket.emitChanged(`database-server-version-changed
|
|
58
|
+
socket.emitChanged(`database-server-version-changed`, { conid, database });
|
|
58
59
|
},
|
|
59
60
|
|
|
60
61
|
handle_error(conid, database, props) {
|
|
@@ -72,7 +73,7 @@ module.exports = {
|
|
|
72
73
|
if (!existing) return;
|
|
73
74
|
if (existing.status && status && existing.status.counter > status.counter) return;
|
|
74
75
|
existing.status = status;
|
|
75
|
-
socket.emitChanged(`database-status-changed
|
|
76
|
+
socket.emitChanged(`database-status-changed`, { conid, database });
|
|
76
77
|
},
|
|
77
78
|
|
|
78
79
|
handle_ping() {},
|
|
@@ -81,6 +82,9 @@ module.exports = {
|
|
|
81
82
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
82
83
|
if (existing) return existing;
|
|
83
84
|
const connection = await connections.getCore({ conid });
|
|
85
|
+
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
|
86
|
+
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
|
87
|
+
}
|
|
84
88
|
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
|
85
89
|
'--is-forked-api',
|
|
86
90
|
'--start-process',
|
|
@@ -313,7 +317,7 @@ module.exports = {
|
|
|
313
317
|
},
|
|
314
318
|
structure: existing.structure,
|
|
315
319
|
};
|
|
316
|
-
socket.emitChanged(`database-status-changed
|
|
320
|
+
socket.emitChanged(`database-status-changed`, { conid, database });
|
|
317
321
|
}
|
|
318
322
|
},
|
|
319
323
|
|
package/src/controllers/files.js
CHANGED
|
@@ -49,7 +49,7 @@ module.exports = {
|
|
|
49
49
|
async delete({ folder, file }, req) {
|
|
50
50
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
51
51
|
await fs.unlink(path.join(filesdir(), folder, file));
|
|
52
|
-
socket.emitChanged(`files-changed
|
|
52
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
53
53
|
socket.emitChanged(`all-files-changed`);
|
|
54
54
|
return true;
|
|
55
55
|
},
|
|
@@ -58,7 +58,7 @@ module.exports = {
|
|
|
58
58
|
async rename({ folder, file, newFile }, req) {
|
|
59
59
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
60
60
|
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
|
61
|
-
socket.emitChanged(`files-changed
|
|
61
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
62
62
|
socket.emitChanged(`all-files-changed`);
|
|
63
63
|
return true;
|
|
64
64
|
},
|
|
@@ -66,7 +66,7 @@ module.exports = {
|
|
|
66
66
|
refresh_meta: true,
|
|
67
67
|
async refresh({ folders }, req) {
|
|
68
68
|
for (const folder of folders) {
|
|
69
|
-
socket.emitChanged(`files-changed
|
|
69
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
70
70
|
socket.emitChanged(`all-files-changed`);
|
|
71
71
|
}
|
|
72
72
|
return true;
|
|
@@ -76,7 +76,7 @@ module.exports = {
|
|
|
76
76
|
async copy({ folder, file, newFile }, req) {
|
|
77
77
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
78
78
|
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
|
79
|
-
socket.emitChanged(`files-changed
|
|
79
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
80
80
|
socket.emitChanged(`all-files-changed`);
|
|
81
81
|
return true;
|
|
82
82
|
},
|
|
@@ -112,13 +112,13 @@ module.exports = {
|
|
|
112
112
|
if (!hasPermission(`archive/write`, req)) return false;
|
|
113
113
|
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
|
|
114
114
|
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
|
115
|
-
socket.emitChanged(`archive-files-changed
|
|
115
|
+
socket.emitChanged(`archive-files-changed`, { folder: folder.substring('archive:'.length) });
|
|
116
116
|
return true;
|
|
117
117
|
} else if (folder.startsWith('app:')) {
|
|
118
118
|
if (!hasPermission(`apps/write`, req)) return false;
|
|
119
119
|
const app = folder.substring('app:'.length);
|
|
120
120
|
await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
|
|
121
|
-
socket.emitChanged(`app-files-changed
|
|
121
|
+
socket.emitChanged(`app-files-changed`, { app });
|
|
122
122
|
socket.emitChanged('used-apps-changed');
|
|
123
123
|
apps.emitChangedDbApp(folder);
|
|
124
124
|
return true;
|
|
@@ -129,7 +129,7 @@ module.exports = {
|
|
|
129
129
|
await fs.mkdir(dir);
|
|
130
130
|
}
|
|
131
131
|
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
|
132
|
-
socket.emitChanged(`files-changed
|
|
132
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
133
133
|
socket.emitChanged(`all-files-changed`);
|
|
134
134
|
if (folder == 'shell') {
|
|
135
135
|
scheduler.reload();
|
|
@@ -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
|
};
|
|
@@ -150,6 +150,31 @@ module.exports = {
|
|
|
150
150
|
return true;
|
|
151
151
|
},
|
|
152
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
|
+
|
|
153
178
|
// cancel_meta: true,
|
|
154
179
|
// async cancel({ sesid }) {
|
|
155
180
|
// const session = this.opened.find((x) => x.sesid == sesid);
|
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;
|
|
@@ -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
|
};
|
|
@@ -16,6 +16,7 @@ let storedConnection;
|
|
|
16
16
|
let afterConnectCallbacks = [];
|
|
17
17
|
// let currentHandlers = [];
|
|
18
18
|
let lastPing = null;
|
|
19
|
+
let currentProfiler = null;
|
|
19
20
|
|
|
20
21
|
class TableWriter {
|
|
21
22
|
constructor() {
|
|
@@ -210,6 +211,31 @@ function waitConnected() {
|
|
|
210
211
|
});
|
|
211
212
|
}
|
|
212
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
|
+
|
|
213
239
|
async function handleExecuteQuery({ sql }) {
|
|
214
240
|
await waitConnected();
|
|
215
241
|
const driver = requireEngineDriver(storedConnection);
|
|
@@ -280,6 +306,8 @@ const messageHandlers = {
|
|
|
280
306
|
connect: handleConnect,
|
|
281
307
|
executeQuery: handleExecuteQuery,
|
|
282
308
|
executeReader: handleExecuteReader,
|
|
309
|
+
startProfiler: handleStartProfiler,
|
|
310
|
+
stopProfiler: handleStopProfiler,
|
|
283
311
|
ping: handlePing,
|
|
284
312
|
// cancel: handleCancel,
|
|
285
313
|
};
|
|
@@ -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
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const express = require('express');
|
|
3
3
|
const getExpressPath = require('./getExpressPath');
|
|
4
|
+
const { MissingCredentialsError } = require('./exceptions');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @param {string} route
|
|
@@ -37,6 +38,13 @@ module.exports = function useController(app, electron, route, controller) {
|
|
|
37
38
|
if (data === undefined) return null;
|
|
38
39
|
return data;
|
|
39
40
|
} catch (err) {
|
|
41
|
+
if (err instanceof MissingCredentialsError) {
|
|
42
|
+
return {
|
|
43
|
+
missingCredentials: true,
|
|
44
|
+
apiErrorMessage: 'Missing credentials',
|
|
45
|
+
detail: err.detail,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
40
48
|
return { apiErrorMessage: err.message };
|
|
41
49
|
}
|
|
42
50
|
});
|
|
@@ -69,7 +77,15 @@ module.exports = function useController(app, electron, route, controller) {
|
|
|
69
77
|
res.json(data);
|
|
70
78
|
} catch (e) {
|
|
71
79
|
console.log(e);
|
|
72
|
-
|
|
80
|
+
if (e instanceof MissingCredentialsError) {
|
|
81
|
+
res.json({
|
|
82
|
+
missingCredentials: true,
|
|
83
|
+
apiErrorMessage: 'Missing credentials',
|
|
84
|
+
detail: e.detail,
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
res.status(500).json({ apiErrorMessage: e.message });
|
|
88
|
+
}
|
|
73
89
|
}
|
|
74
90
|
});
|
|
75
91
|
}
|