mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-25 16:35:59 +00:00
Merge branch 'develop'
This commit is contained in:
@@ -121,6 +121,7 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"better-sqlite3": "9.6.0",
|
"better-sqlite3": "9.6.0",
|
||||||
"msnodesqlv8": "^4.2.1"
|
"msnodesqlv8": "^4.2.1",
|
||||||
|
"oracledb": "^6.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,11 +365,13 @@ function createWindow() {
|
|||||||
console.log('Error saving config-root:', err.message);
|
console.log('Error saving config-root:', err.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// mainWindow.webContents.toggleDevTools();
|
||||||
|
|
||||||
mainWindow.loadURL(startUrl);
|
mainWindow.loadURL(startUrl);
|
||||||
if (os.platform() == 'linux') {
|
if (os.platform() == 'linux') {
|
||||||
mainWindow.setIcon(path.resolve(__dirname, '../icon.png'));
|
mainWindow.setIcon(path.resolve(__dirname, '../icon.png'));
|
||||||
}
|
}
|
||||||
// mainWindow.webContents.toggleDevTools();
|
|
||||||
|
|
||||||
mainWindow.on('maximize', () => {
|
mainWindow.on('maximize', () => {
|
||||||
mainWindow.webContents.send('setIsMaximized', true);
|
mainWindow.webContents.send('setIsMaximized', true);
|
||||||
|
|||||||
@@ -1944,6 +1944,11 @@ open@^7.4.2:
|
|||||||
is-docker "^2.0.0"
|
is-docker "^2.0.0"
|
||||||
is-wsl "^2.1.1"
|
is-wsl "^2.1.1"
|
||||||
|
|
||||||
|
oracledb@^6.6.0:
|
||||||
|
version "6.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/oracledb/-/oracledb-6.6.0.tgz#bb40adbe81a84a1e544c48af9f120c61f030e936"
|
||||||
|
integrity sha512-T3dx+o3j+tVN53wQyr4yGTmoPHLy+a2V8yb1T2PmWrsj3ZlSt2Yu1BgV2yTDqnmBZYpRi/I3yJXRCOHHD7PiyA==
|
||||||
|
|
||||||
os-tmpdir@~1.0.2:
|
os-tmpdir@~1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ const fs = require('fs');
|
|||||||
let fillContent = '';
|
let fillContent = '';
|
||||||
|
|
||||||
if (process.platform == 'win32') {
|
if (process.platform == 'win32') {
|
||||||
fillContent += `content.msnodesqlv8 = () => require('msnodesqlv8');`;
|
fillContent += `content.msnodesqlv8 = () => require('msnodesqlv8');\n`;
|
||||||
}
|
}
|
||||||
fillContent += `content['better-sqlite3'] = () => require('better-sqlite3');`;
|
fillContent += `content['better-sqlite3'] = () => require('better-sqlite3');\n`;
|
||||||
|
fillContent += `content['oracledb'] = () => require('oracledb');\n`;
|
||||||
|
|
||||||
const getContent = empty => `
|
const getContent = empty => `
|
||||||
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
|
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
"start:api:singledb": "yarn workspace dbgate-api start:singledb | pino-pretty",
|
"start:api:singledb": "yarn workspace dbgate-api start:singledb | pino-pretty",
|
||||||
"start:api:auth": "yarn workspace dbgate-api start:auth | pino-pretty",
|
"start:api:auth": "yarn workspace dbgate-api start:auth | pino-pretty",
|
||||||
"start:api:dblogin": "yarn workspace dbgate-api start:dblogin | pino-pretty",
|
"start:api:dblogin": "yarn workspace dbgate-api start:dblogin | pino-pretty",
|
||||||
|
"start:api:storage": "yarn workspace dbgate-api start:storage | pino-pretty",
|
||||||
|
"sync:pro": "cd sync && yarn start",
|
||||||
"start:web": "yarn workspace dbgate-web dev",
|
"start:web": "yarn workspace dbgate-web dev",
|
||||||
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
||||||
"start:tools": "yarn workspace dbgate-tools start",
|
"start:tools": "yarn workspace dbgate-tools start",
|
||||||
@@ -35,6 +37,7 @@
|
|||||||
"build:web:docker": "yarn workspace dbgate-web build",
|
"build:web:docker": "yarn workspace dbgate-web build",
|
||||||
"build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
|
"build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
|
||||||
"build:plugins:frontend:watch": "workspaces-run --parallel --only=\"dbgate-plugin-*\" -- yarn build:frontend:watch",
|
"build:plugins:frontend:watch": "workspaces-run --parallel --only=\"dbgate-plugin-*\" -- yarn build:frontend:watch",
|
||||||
|
"storage-json": "dbmodel model-to-json storage-db packages/api/src/storageModel.js --commonjs",
|
||||||
"plugins:copydist": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn copydist",
|
"plugins:copydist": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn copydist",
|
||||||
"build:app:local": "yarn plugins:copydist && cd app && yarn build:local",
|
"build:app:local": "yarn plugins:copydist && cd app && yarn build:local",
|
||||||
"start:app:local": "cd app && yarn start:local",
|
"start:app:local": "cd app && yarn start:local",
|
||||||
@@ -48,8 +51,8 @@
|
|||||||
"resetPackagedPlugins": "node resetPackagedPlugins",
|
"resetPackagedPlugins": "node resetPackagedPlugins",
|
||||||
"prettier": "prettier --write packages/api/src && prettier --write packages/datalib/src && prettier --write packages/filterparser/src && prettier --write packages/sqltree/src && prettier --write packages/tools/src && prettier --write packages/types && prettier --write packages/web/src && prettier --write app/src",
|
"prettier": "prettier --write packages/api/src && prettier --write packages/datalib/src && prettier --write packages/filterparser/src && prettier --write packages/sqltree/src && prettier --write packages/tools/src && prettier --write packages/types && prettier --write packages/web/src && prettier --write app/src",
|
||||||
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/public/* docker -u 2 && copyfiles \"packages/web/public/**/*\" docker -u 2 && copyfiles \"plugins/dist/**/*\" docker/plugins -u 2",
|
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/public/* docker -u 2 && copyfiles \"packages/web/public/**/*\" docker -u 2 && copyfiles \"plugins/dist/**/*\" docker/plugins -u 2",
|
||||||
"install:sqlite:docker": "cd docker && yarn init --yes && yarn add better-sqlite3 && cd ..",
|
"install:drivers:docker": "cd docker && yarn init --yes && yarn add better-sqlite3 && yarn add oracledb && cd ..",
|
||||||
"prepare:docker": "yarn plugins:copydist && yarn build:web:docker && yarn build:api && yarn copy:docker:build && yarn install:sqlite:docker",
|
"prepare:docker": "yarn plugins:copydist && yarn build:web:docker && yarn build:api && yarn copy:docker:build && yarn install:drivers:docker",
|
||||||
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
|
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
|
||||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"",
|
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"",
|
||||||
"ts:api": "yarn workspace dbgate-api ts",
|
"ts:api": "yarn workspace dbgate-api ts",
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
||||||
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
|
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
|
||||||
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
||||||
|
"start:storage": "env-cmd -f env/storage/.env node src/index.js --listen-api",
|
||||||
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
||||||
"ts": "tsc",
|
"ts": "tsc",
|
||||||
"build": "webpack"
|
"build": "webpack"
|
||||||
@@ -83,6 +84,7 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"better-sqlite3": "9.6.0",
|
"better-sqlite3": "9.6.0",
|
||||||
"msnodesqlv8": "^4.2.1"
|
"msnodesqlv8": "^4.2.1",
|
||||||
|
"oracledb": "^6.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
packages/api/src/auth/authCommon.js
Normal file
16
packages/api/src/auth/authCommon.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const tokenSecret = crypto.randomUUID();
|
||||||
|
|
||||||
|
function getTokenLifetime() {
|
||||||
|
return process.env.TOKEN_LIFETIME || '1d';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokenSecret() {
|
||||||
|
return tokenSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getTokenLifetime,
|
||||||
|
getTokenSecret,
|
||||||
|
};
|
||||||
251
packages/api/src/auth/authProvider.js
Normal file
251
packages/api/src/auth/authProvider.js
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
const { getTokenSecret, getTokenLifetime } = require('./authCommon');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const axios = require('axios');
|
||||||
|
const { getLogger } = require('dbgate-tools');
|
||||||
|
|
||||||
|
const AD = require('activedirectory2').promiseWrapper;
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
const logger = getLogger('authProvider');
|
||||||
|
|
||||||
|
class AuthProviderBase {
|
||||||
|
async login(login, password, options = undefined) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldAuthorizeApi() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthToken(params) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentLogin(req) {
|
||||||
|
const login = req?.user?.login ?? req?.auth?.user ?? null;
|
||||||
|
return login;
|
||||||
|
}
|
||||||
|
|
||||||
|
isUserLoggedIn(req) {
|
||||||
|
return !!req?.user || !!req?.auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentPermissions(req) {
|
||||||
|
const login = this.getCurrentLogin(req);
|
||||||
|
const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
|
||||||
|
return permissions || process.env.PERMISSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoginForm() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdditionalConfigProps() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoginPageConnections() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSingleConnectionId(req) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OAuthProvider extends AuthProviderBase {
|
||||||
|
shouldAuthorizeApi() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async oauthToken(params) {
|
||||||
|
const { redirectUri, code } = params;
|
||||||
|
|
||||||
|
const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : '';
|
||||||
|
const resp = await axios.default.post(
|
||||||
|
`${process.env.OAUTH_TOKEN}`,
|
||||||
|
`grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent(
|
||||||
|
redirectUri
|
||||||
|
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}${scopeParam}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const { access_token, refresh_token } = resp.data;
|
||||||
|
|
||||||
|
const payload = jwt.decode(access_token);
|
||||||
|
|
||||||
|
logger.info({ payload }, 'User payload returned from OAUTH');
|
||||||
|
|
||||||
|
const login =
|
||||||
|
process.env.OAUTH_LOGIN_FIELD && payload && payload[process.env.OAUTH_LOGIN_FIELD]
|
||||||
|
? payload[process.env.OAUTH_LOGIN_FIELD]
|
||||||
|
: 'oauth';
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env.OAUTH_ALLOWED_LOGINS &&
|
||||||
|
!process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
||||||
|
) {
|
||||||
|
return { error: `Username ${login} not allowed to log in` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups =
|
||||||
|
process.env.OAUTH_GROUP_FIELD && payload && payload[process.env.OAUTH_GROUP_FIELD]
|
||||||
|
? payload[process.env.OAUTH_GROUP_FIELD]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const allowedGroups = process.env.OAUTH_ALLOWED_GROUPS
|
||||||
|
? process.env.OAUTH_ALLOWED_GROUPS.split(',').map(group => group.toLowerCase().trim())
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (process.env.OAUTH_ALLOWED_GROUPS && !groups.some(group => allowedGroups.includes(group.toLowerCase().trim()))) {
|
||||||
|
return { error: `Username ${login} does not belong to an allowed group` };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (access_token) {
|
||||||
|
return {
|
||||||
|
accessToken: jwt.sign({ login }, getTokenSecret(), { expiresIn: getTokenLifetime() }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { error: 'Token not found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdditionalConfigProps() {
|
||||||
|
return {
|
||||||
|
oauth: process.env.OAUTH_AUTH,
|
||||||
|
oauthClient: process.env.OAUTH_CLIENT_ID,
|
||||||
|
oauthScope: process.env.OAUTH_SCOPE,
|
||||||
|
oauthLogout: process.env.OAUTH_LOGOUT,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ADProvider extends AuthProviderBase {
|
||||||
|
async login(login, password) {
|
||||||
|
const adConfig = {
|
||||||
|
url: process.env.AD_URL,
|
||||||
|
baseDN: process.env.AD_BASEDN,
|
||||||
|
username: process.env.AD_USERNAME,
|
||||||
|
password: process.env.AD_PASSWORD,
|
||||||
|
};
|
||||||
|
const ad = new AD(adConfig);
|
||||||
|
try {
|
||||||
|
const res = await ad.authenticate(login, password);
|
||||||
|
if (!res) {
|
||||||
|
return { error: 'Login failed' };
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
process.env.AD_ALLOWED_LOGINS &&
|
||||||
|
!process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
||||||
|
) {
|
||||||
|
return { error: `Username ${login} not allowed to log in` };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
accessToken: jwt.sign({ login }, getTokenSecret(), { expiresIn: getTokenLifetime() }),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return { error: 'Login failed' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldAuthorizeApi() {
|
||||||
|
return !process.env.BASIC_AUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoginForm() {
|
||||||
|
return !process.env.BASIC_AUTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginsProvider extends AuthProviderBase {
|
||||||
|
async login(login, password) {
|
||||||
|
if (password == process.env[`LOGIN_PASSWORD_${login}`]) {
|
||||||
|
return {
|
||||||
|
accessToken: jwt.sign({ login }, getTokenSecret(), { expiresIn: getTokenLifetime() }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { error: 'Invalid credentials' };
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldAuthorizeApi() {
|
||||||
|
return !process.env.BASIC_AUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoginForm() {
|
||||||
|
return !process.env.BASIC_AUTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DenyAllProvider extends AuthProviderBase {
|
||||||
|
shouldAuthorizeApi() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(login, password) {
|
||||||
|
return { error: 'Login not allowed' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasEnvLogins() {
|
||||||
|
if (process.env.LOGIN && process.env.PASSWORD) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const key in process.env) {
|
||||||
|
if (key.startsWith('LOGIN_PASSWORD_')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectEnvAuthProvider() {
|
||||||
|
if (process.env.AUTH_PROVIDER) {
|
||||||
|
return process.env.AUTH_PROVIDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.STORAGE_DATABASE) {
|
||||||
|
return 'denyall';
|
||||||
|
}
|
||||||
|
if (process.env.OAUTH_AUTH) {
|
||||||
|
return 'oauth';
|
||||||
|
}
|
||||||
|
if (process.env.AD_URL) {
|
||||||
|
return 'ad';
|
||||||
|
}
|
||||||
|
if (hasEnvLogins()) {
|
||||||
|
return 'logins';
|
||||||
|
}
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEnvAuthProvider() {
|
||||||
|
const authProvider = detectEnvAuthProvider();
|
||||||
|
switch (authProvider) {
|
||||||
|
case 'oauth':
|
||||||
|
return new OAuthProvider();
|
||||||
|
case 'ad':
|
||||||
|
return new ADProvider();
|
||||||
|
case 'logins':
|
||||||
|
return new LoginsProvider();
|
||||||
|
case 'denyall':
|
||||||
|
return new DenyAllProvider();
|
||||||
|
default:
|
||||||
|
return new AuthProviderBase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let authProvider = createEnvAuthProvider();
|
||||||
|
|
||||||
|
function getAuthProvider() {
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAuthProvider(value) {
|
||||||
|
authProvider = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
AuthProviderBase,
|
||||||
|
detectEnvAuthProvider,
|
||||||
|
getAuthProvider,
|
||||||
|
setAuthProvider,
|
||||||
|
};
|
||||||
@@ -1,24 +1,15 @@
|
|||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const getExpressPath = require('../utility/getExpressPath');
|
const getExpressPath = require('../utility/getExpressPath');
|
||||||
const { getLogins } = require('../utility/hasPermission');
|
|
||||||
const { getLogger } = require('dbgate-tools');
|
const { getLogger } = require('dbgate-tools');
|
||||||
const AD = require('activedirectory2').promiseWrapper;
|
const AD = require('activedirectory2').promiseWrapper;
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const { getTokenSecret, getTokenLifetime } = require('../auth/authCommon');
|
||||||
|
const { getAuthProvider } = require('../auth/authProvider');
|
||||||
|
const storage = require('./storage');
|
||||||
|
|
||||||
const logger = getLogger('auth');
|
const logger = getLogger('auth');
|
||||||
|
|
||||||
const tokenSecret = crypto.randomUUID();
|
|
||||||
|
|
||||||
function shouldAuthorizeApi() {
|
|
||||||
const logins = getLogins();
|
|
||||||
return !!process.env.OAUTH_AUTH || !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTokenLifetime() {
|
|
||||||
return process.env.TOKEN_LIFETIME || '1d';
|
|
||||||
}
|
|
||||||
|
|
||||||
function unauthorizedResponse(req, res, text) {
|
function unauthorizedResponse(req, res, text) {
|
||||||
// if (req.path == getExpressPath('/config/get-settings')) {
|
// if (req.path == getExpressPath('/config/get-settings')) {
|
||||||
// return res.json({});
|
// return res.json({});
|
||||||
@@ -30,9 +21,23 @@ function unauthorizedResponse(req, res, text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function authMiddleware(req, res, next) {
|
function authMiddleware(req, res, next) {
|
||||||
const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', '/auth/login', '/stream'];
|
const SKIP_AUTH_PATHS = [
|
||||||
|
'/config/get',
|
||||||
|
'/config/get-settings',
|
||||||
|
'/auth/oauth-token',
|
||||||
|
'/auth/login',
|
||||||
|
'/stream',
|
||||||
|
'storage/get-connections-for-login-page',
|
||||||
|
'/connections/dblogin',
|
||||||
|
'/connections/dblogin-auth',
|
||||||
|
'/connections/dblogin-auth-token',
|
||||||
|
];
|
||||||
|
|
||||||
if (!shouldAuthorizeApi()) {
|
// console.log('********************* getAuthProvider()', getAuthProvider());
|
||||||
|
|
||||||
|
const isAdminPage = req.headers['x-is-admin-page'] == 'true';
|
||||||
|
|
||||||
|
if (!isAdminPage && !getAuthProvider().shouldAuthorizeApi()) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x));
|
let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x));
|
||||||
@@ -46,7 +51,7 @@ function authMiddleware(req, res, next) {
|
|||||||
}
|
}
|
||||||
const token = authHeader.split(' ')[1];
|
const token = authHeader.split(' ')[1];
|
||||||
try {
|
try {
|
||||||
const decoded = jwt.verify(token, tokenSecret);
|
const decoded = jwt.verify(token, getTokenSecret());
|
||||||
req.user = decoded;
|
req.user = decoded;
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -63,106 +68,34 @@ function authMiddleware(req, res, next) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
oauthToken_meta: true,
|
oauthToken_meta: true,
|
||||||
async oauthToken(params) {
|
async oauthToken(params) {
|
||||||
const { redirectUri, code } = params;
|
return getAuthProvider().oauthToken(params);
|
||||||
|
|
||||||
const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : '';
|
|
||||||
const resp = await axios.default.post(
|
|
||||||
`${process.env.OAUTH_TOKEN}`,
|
|
||||||
`grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent(
|
|
||||||
redirectUri
|
|
||||||
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}${scopeParam}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const { access_token, refresh_token } = resp.data;
|
|
||||||
|
|
||||||
const payload = jwt.decode(access_token);
|
|
||||||
|
|
||||||
logger.info({ payload }, 'User payload returned from OAUTH');
|
|
||||||
|
|
||||||
const login =
|
|
||||||
process.env.OAUTH_LOGIN_FIELD && payload && payload[process.env.OAUTH_LOGIN_FIELD]
|
|
||||||
? payload[process.env.OAUTH_LOGIN_FIELD]
|
|
||||||
: 'oauth';
|
|
||||||
|
|
||||||
if (
|
|
||||||
process.env.OAUTH_ALLOWED_LOGINS &&
|
|
||||||
!process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
|
||||||
) {
|
|
||||||
return { error: `Username ${login} not allowed to log in` };
|
|
||||||
}
|
|
||||||
|
|
||||||
const groups =
|
|
||||||
process.env.OAUTH_GROUP_FIELD && payload && payload[process.env.OAUTH_GROUP_FIELD]
|
|
||||||
? payload[process.env.OAUTH_GROUP_FIELD]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const allowedGroups =
|
|
||||||
process.env.OAUTH_ALLOWED_GROUPS
|
|
||||||
? process.env.OAUTH_ALLOWED_GROUPS.split(',').map(group => group.toLowerCase().trim())
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (
|
|
||||||
process.env.OAUTH_ALLOWED_GROUPS &&
|
|
||||||
!groups.some(group => allowedGroups.includes(group.toLowerCase().trim()))
|
|
||||||
) {
|
|
||||||
return { error: `Username ${login} does not belong to an allowed group` };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (access_token) {
|
|
||||||
return {
|
|
||||||
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { error: 'Token not found' };
|
|
||||||
},
|
},
|
||||||
login_meta: true,
|
login_meta: true,
|
||||||
async login(params) {
|
async login(params) {
|
||||||
const { login, password } = params;
|
const { login, password, isAdminPage } = params;
|
||||||
|
|
||||||
if (process.env.AD_URL) {
|
if (isAdminPage) {
|
||||||
const adConfig = {
|
if (process.env.ADMIN_PASSWORD && process.env.ADMIN_PASSWORD == password) {
|
||||||
url: process.env.AD_URL,
|
|
||||||
baseDN: process.env.AD_BASEDN,
|
|
||||||
username: process.env.AD_USERNAME,
|
|
||||||
password: process.env.AD_PASSOWRD,
|
|
||||||
};
|
|
||||||
const ad = new AD(adConfig);
|
|
||||||
try {
|
|
||||||
const res = await ad.authenticate(login, password);
|
|
||||||
if (!res) {
|
|
||||||
return { error: 'Login failed' };
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
process.env.AD_ALLOWED_LOGINS &&
|
|
||||||
!process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
|
||||||
) {
|
|
||||||
return { error: `Username ${login} not allowed to log in` };
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
accessToken: jwt.sign(
|
||||||
};
|
{
|
||||||
} catch (err) {
|
login: 'superadmin',
|
||||||
logger.error({ err }, 'Failed active directory authentization');
|
permissions: await storage.loadSuperadminPermissions(),
|
||||||
return {
|
roleId: -3,
|
||||||
error: err.message,
|
},
|
||||||
|
getTokenSecret(),
|
||||||
|
{
|
||||||
|
expiresIn: getTokenLifetime(),
|
||||||
|
}
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { error: 'Login failed' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const logins = getLogins();
|
return getAuthProvider().login(login, password);
|
||||||
if (!logins) {
|
|
||||||
return { error: 'Logins not configured' };
|
|
||||||
}
|
|
||||||
const foundLogin = logins.find(x => x.login == login);
|
|
||||||
if (foundLogin && foundLogin.password && foundLogin.password == password) {
|
|
||||||
return {
|
|
||||||
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { error: 'Invalid credentials' };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
authMiddleware,
|
authMiddleware,
|
||||||
shouldAuthorizeApi,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const os = require('os');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const { datadir, getLogsFilePath } = require('../utility/directories');
|
const { datadir, getLogsFilePath } = require('../utility/directories');
|
||||||
const { hasPermission, getLogins } = require('../utility/hasPermission');
|
const { hasPermission } = require('../utility/hasPermission');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const AsyncLock = require('async-lock');
|
const AsyncLock = require('async-lock');
|
||||||
@@ -11,6 +11,7 @@ const AsyncLock = require('async-lock');
|
|||||||
const currentVersion = require('../currentVersion');
|
const currentVersion = require('../currentVersion');
|
||||||
const platformInfo = require('../utility/platformInfo');
|
const platformInfo = require('../utility/platformInfo');
|
||||||
const connections = require('../controllers/connections');
|
const connections = require('../controllers/connections');
|
||||||
|
const { getAuthProvider } = require('../auth/authProvider');
|
||||||
|
|
||||||
const lock = new AsyncLock();
|
const lock = new AsyncLock();
|
||||||
|
|
||||||
@@ -27,27 +28,37 @@ module.exports = {
|
|||||||
|
|
||||||
get_meta: true,
|
get_meta: true,
|
||||||
async get(_params, req) {
|
async get(_params, req) {
|
||||||
const logins = getLogins();
|
const authProvider = getAuthProvider();
|
||||||
const loginName =
|
const login = authProvider.getCurrentLogin(req);
|
||||||
req && req.user && req.user.login ? req.user.login : req && req.auth && req.auth.user ? req.auth.user : null;
|
const permissions = authProvider.getCurrentPermissions(req);
|
||||||
const login = logins && loginName ? logins.find(x => x.login == loginName) : null;
|
const isLoginForm = authProvider.isLoginForm();
|
||||||
const permissions = login ? login.permissions : process.env.PERMISSIONS;
|
const additionalConfigProps = authProvider.getAdditionalConfigProps();
|
||||||
|
const isUserLoggedIn = authProvider.isUserLoggedIn(req);
|
||||||
|
|
||||||
|
const singleConid = authProvider.getSingleConnectionId(req);
|
||||||
|
|
||||||
|
const singleConnection = singleConid
|
||||||
|
? await connections.getCore({ conid: singleConid })
|
||||||
|
: connections.singleConnection;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
runAsPortal: !!connections.portalConnections,
|
runAsPortal: !!connections.portalConnections,
|
||||||
singleDbConnection: connections.singleDbConnection,
|
singleDbConnection: connections.singleDbConnection,
|
||||||
singleConnection: connections.singleConnection,
|
singleConnection: singleConnection,
|
||||||
|
isUserLoggedIn,
|
||||||
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
||||||
allowShellConnection: platformInfo.allowShellConnection,
|
allowShellConnection: platformInfo.allowShellConnection,
|
||||||
allowShellScripting: platformInfo.allowShellScripting,
|
allowShellScripting: platformInfo.allowShellScripting,
|
||||||
isDocker: platformInfo.isDocker,
|
isDocker: platformInfo.isDocker,
|
||||||
|
isElectron: platformInfo.isElectron,
|
||||||
|
isLicenseValid: platformInfo.isLicenseValid,
|
||||||
|
licenseError: platformInfo.licenseError,
|
||||||
permissions,
|
permissions,
|
||||||
login,
|
login,
|
||||||
oauth: process.env.OAUTH_AUTH,
|
...additionalConfigProps,
|
||||||
oauthClient: process.env.OAUTH_CLIENT_ID,
|
isLoginForm,
|
||||||
oauthScope: process.env.OAUTH_SCOPE,
|
isAdminLoginForm: !!(process.env.STORAGE_DATABASE && process.env.ADMIN_PASSWORD && !process.env.BASIC_AUTH),
|
||||||
oauthLogout: process.env.OAUTH_LOGOUT,
|
storageDatabase: process.env.STORAGE_DATABASE,
|
||||||
isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH),
|
|
||||||
logsFilePath: getLogsFilePath(),
|
logsFilePath: getLogsFilePath(),
|
||||||
connectionsFilePath: path.join(datadir(), 'connections.jsonl'),
|
connectionsFilePath: path.join(datadir(), 'connections.jsonl'),
|
||||||
...currentVersion,
|
...currentVersion,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const { safeJsonParse, getLogger } = require('dbgate-tools');
|
|||||||
const platformInfo = require('../utility/platformInfo');
|
const platformInfo = require('../utility/platformInfo');
|
||||||
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
||||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||||
|
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||||
|
const { getAuthProvider } = require('../auth/authProvider');
|
||||||
|
|
||||||
const logger = getLogger('connections');
|
const logger = getLogger('connections');
|
||||||
|
|
||||||
@@ -199,6 +201,12 @@ module.exports = {
|
|||||||
|
|
||||||
list_meta: true,
|
list_meta: true,
|
||||||
async list(_params, req) {
|
async list(_params, req) {
|
||||||
|
const storage = require('./storage');
|
||||||
|
|
||||||
|
const storageConnections = await storage.connections(req);
|
||||||
|
if (storageConnections) {
|
||||||
|
return storageConnections;
|
||||||
|
}
|
||||||
if (portalConnections) {
|
if (portalConnections) {
|
||||||
if (platformInfo.allowShellConnection) return portalConnections;
|
if (platformInfo.allowShellConnection) return portalConnections;
|
||||||
return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, req));
|
return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, req));
|
||||||
@@ -236,14 +244,16 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
saveVolatile_meta: true,
|
saveVolatile_meta: true,
|
||||||
async saveVolatile({ conid, user, password, test }) {
|
async saveVolatile({ conid, user = undefined, password = undefined, accessToken = undefined, test = false }) {
|
||||||
const old = await this.getCore({ conid });
|
const old = await this.getCore({ conid });
|
||||||
const res = {
|
const res = {
|
||||||
...old,
|
...old,
|
||||||
_id: crypto.randomUUID(),
|
_id: crypto.randomUUID(),
|
||||||
password,
|
password,
|
||||||
|
accessToken,
|
||||||
passwordMode: undefined,
|
passwordMode: undefined,
|
||||||
unsaved: true,
|
unsaved: true,
|
||||||
|
useRedirectDbLogin: false,
|
||||||
};
|
};
|
||||||
if (old.passwordMode == 'askUser') {
|
if (old.passwordMode == 'askUser') {
|
||||||
res.user = user;
|
res.user = user;
|
||||||
@@ -336,6 +346,14 @@ module.exports = {
|
|||||||
if (volatile) {
|
if (volatile) {
|
||||||
return volatile;
|
return volatile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storage = require('./storage');
|
||||||
|
|
||||||
|
const storageConnection = await storage.getConnection({ conid });
|
||||||
|
if (storageConnection) {
|
||||||
|
return storageConnection;
|
||||||
|
}
|
||||||
|
|
||||||
if (portalConnections) {
|
if (portalConnections) {
|
||||||
const res = portalConnections.find(x => x._id == conid) || null;
|
const res = portalConnections.find(x => x._id == conid) || null;
|
||||||
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
||||||
@@ -365,4 +383,64 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dblogin_meta: {
|
||||||
|
raw: true,
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
|
async dblogin(req, res) {
|
||||||
|
const { conid, state, redirectUri } = req.query;
|
||||||
|
const connection = await this.getCore({ conid });
|
||||||
|
const driver = requireEngineDriver(connection);
|
||||||
|
const authUrl = await driver.getRedirectAuthUrl(connection, { redirectUri, state });
|
||||||
|
res.redirect(authUrl);
|
||||||
|
},
|
||||||
|
|
||||||
|
dbloginToken_meta: true,
|
||||||
|
async dbloginToken({ code, conid, strmid, redirectUri }) {
|
||||||
|
try {
|
||||||
|
const connection = await this.getCore({ conid });
|
||||||
|
const driver = requireEngineDriver(connection);
|
||||||
|
const accessToken = await driver.getAuthTokenFromCode(connection, { code, redirectUri });
|
||||||
|
const volatile = await this.saveVolatile({ conid, accessToken });
|
||||||
|
// console.log('******************************** WE HAVE ACCESS TOKEN', accessToken);
|
||||||
|
socket.emit('got-volatile-token', { strmid, savedConId: conid, volatileConId: volatile._id });
|
||||||
|
return { success: true };
|
||||||
|
} catch (err) {
|
||||||
|
logger.error({ err }, 'Error getting DB token');
|
||||||
|
return { error: err.message };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
dbloginAuthToken_meta: true,
|
||||||
|
async dbloginAuthToken({ code, conid, redirectUri }) {
|
||||||
|
try {
|
||||||
|
const connection = await this.getCore({ conid });
|
||||||
|
const driver = requireEngineDriver(connection);
|
||||||
|
const accessToken = await driver.getAuthTokenFromCode(connection, { code, redirectUri });
|
||||||
|
const volatile = await this.saveVolatile({ conid, accessToken });
|
||||||
|
const authProvider = getAuthProvider();
|
||||||
|
const resp = await authProvider.login(null, null, { conid: volatile._id });
|
||||||
|
return resp;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error({ err }, 'Error getting DB token');
|
||||||
|
return { error: err.message };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
dbloginAuth_meta: true,
|
||||||
|
async dbloginAuth({ conid, user, password }) {
|
||||||
|
if (user || password) {
|
||||||
|
const saveResp = await this.saveVolatile({ conid, user, password, test: true });
|
||||||
|
if (saveResp.msgtype == 'connected') {
|
||||||
|
const loginResp = await getAuthProvider().login(user, password, { conid: saveResp._id });
|
||||||
|
return loginResp;
|
||||||
|
}
|
||||||
|
return saveResp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user and password is stored in connection, volatile connection is not needed
|
||||||
|
const loginResp = await getAuthProvider().login(null, null, { conid });
|
||||||
|
return loginResp;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ module.exports = {
|
|||||||
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
||||||
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
||||||
}
|
}
|
||||||
|
if (connection.useRedirectDbLogin) {
|
||||||
|
throw new MissingCredentialsError({ conid, redirectToDbLogin: true });
|
||||||
|
}
|
||||||
const subprocess = fork(
|
const subprocess = fork(
|
||||||
global['API_PACKAGE'] || process.argv[1],
|
global['API_PACKAGE'] || process.argv[1],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ module.exports = {
|
|||||||
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
||||||
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
||||||
}
|
}
|
||||||
const subprocess = fork(
|
if (connection.useRedirectDbLogin) {
|
||||||
|
throw new MissingCredentialsError({ conid, redirectToDbLogin: true });
|
||||||
|
}
|
||||||
|
const subprocess = fork(
|
||||||
global['API_PACKAGE'] || process.argv[1],
|
global['API_PACKAGE'] || process.argv[1],
|
||||||
[
|
[
|
||||||
'--is-forked-api',
|
'--is-forked-api',
|
||||||
|
|||||||
20
packages/api/src/controllers/storage.js
Normal file
20
packages/api/src/controllers/storage.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
connections_meta: true,
|
||||||
|
async connections(req) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
getConnection_meta: true,
|
||||||
|
async getConnection({ conid }) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadSuperadminPermissions() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
|
||||||
|
getConnectionsForLoginPage_meta: true,
|
||||||
|
async getConnectionsForLoginPage() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -98,6 +98,7 @@ if (processArgs.listenApi) {
|
|||||||
|
|
||||||
const shell = require('./shell/index');
|
const shell = require('./shell/index');
|
||||||
const dbgateTools = require('dbgate-tools');
|
const dbgateTools = require('dbgate-tools');
|
||||||
|
const currentVersion = require('./currentVersion');
|
||||||
|
|
||||||
global['DBGATE_TOOLS'] = dbgateTools;
|
global['DBGATE_TOOLS'] = dbgateTools;
|
||||||
|
|
||||||
@@ -116,6 +117,7 @@ module.exports = {
|
|||||||
...shell,
|
...shell,
|
||||||
getLogger,
|
getLogger,
|
||||||
configureLogger,
|
configureLogger,
|
||||||
|
currentVersion,
|
||||||
// loadLogsContent,
|
// loadLogsContent,
|
||||||
getMainModule: () => require('./main'),
|
getMainModule: () => require('./main'),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const sessions = require('./controllers/sessions');
|
|||||||
const runners = require('./controllers/runners');
|
const runners = require('./controllers/runners');
|
||||||
const jsldata = require('./controllers/jsldata');
|
const jsldata = require('./controllers/jsldata');
|
||||||
const config = require('./controllers/config');
|
const config = require('./controllers/config');
|
||||||
|
const storage = require('./controllers/storage');
|
||||||
const archive = require('./controllers/archive');
|
const archive = require('./controllers/archive');
|
||||||
const apps = require('./controllers/apps');
|
const apps = require('./controllers/apps');
|
||||||
const auth = require('./controllers/auth');
|
const auth = require('./controllers/auth');
|
||||||
@@ -31,9 +32,9 @@ const onFinished = require('on-finished');
|
|||||||
const { rundir } = require('./utility/directories');
|
const { rundir } = require('./utility/directories');
|
||||||
const platformInfo = require('./utility/platformInfo');
|
const platformInfo = require('./utility/platformInfo');
|
||||||
const getExpressPath = require('./utility/getExpressPath');
|
const getExpressPath = require('./utility/getExpressPath');
|
||||||
const { getLogins } = require('./utility/hasPermission');
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { getLogger } = require('dbgate-tools');
|
const { getLogger } = require('dbgate-tools');
|
||||||
|
const { getAuthProvider } = require('./auth/authProvider');
|
||||||
|
|
||||||
const logger = getLogger('main');
|
const logger = getLogger('main');
|
||||||
|
|
||||||
@@ -44,11 +45,23 @@ function start() {
|
|||||||
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
const logins = getLogins();
|
if (process.env.BASIC_AUTH) {
|
||||||
if (logins && process.env.BASIC_AUTH) {
|
async function authorizer(username, password, cb) {
|
||||||
|
try {
|
||||||
|
const resp = await getAuthProvider().login(username, password);
|
||||||
|
if (resp.accessToken) {
|
||||||
|
cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(null, false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
cb(err, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
app.use(
|
app.use(
|
||||||
basicAuth({
|
basicAuth({
|
||||||
users: _.fromPairs(logins.filter(x => x.password).map(x => [x.login, x.password])),
|
authorizer,
|
||||||
|
authorizeAsync: true,
|
||||||
challenge: true,
|
challenge: true,
|
||||||
realm: 'DbGate Web App',
|
realm: 'DbGate Web App',
|
||||||
})
|
})
|
||||||
@@ -72,9 +85,7 @@ function start() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.shouldAuthorizeApi()) {
|
app.use(auth.authMiddleware);
|
||||||
app.use(auth.authMiddleware);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get(getExpressPath('/stream'), async function (req, res) {
|
app.get(getExpressPath('/stream'), async function (req, res) {
|
||||||
const strmid = req.query.strmid;
|
const strmid = req.query.strmid;
|
||||||
@@ -162,6 +173,7 @@ function useAllControllers(app, electron) {
|
|||||||
useController(app, electron, '/runners', runners);
|
useController(app, electron, '/runners', runners);
|
||||||
useController(app, electron, '/jsldata', jsldata);
|
useController(app, electron, '/jsldata', jsldata);
|
||||||
useController(app, electron, '/config', config);
|
useController(app, electron, '/config', config);
|
||||||
|
useController(app, electron, '/storage', storage);
|
||||||
useController(app, electron, '/archive', archive);
|
useController(app, electron, '/archive', archive);
|
||||||
useController(app, electron, '/uploads', uploads);
|
useController(app, electron, '/uploads', uploads);
|
||||||
useController(app, electron, '/plugins', plugins);
|
useController(app, electron, '/plugins', plugins);
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ async function getSshConnection(connection) {
|
|||||||
agentForward: connection.sshMode == 'agent',
|
agentForward: connection.sshMode == 'agent',
|
||||||
passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyfilePassword : undefined,
|
passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyfilePassword : undefined,
|
||||||
username: connection.sshLogin,
|
username: connection.sshLogin,
|
||||||
password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
|
password: (connection.sshMode || 'userPassword') == 'userPassword' ? connection.sshPassword : undefined,
|
||||||
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
|
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
|
||||||
privateKey:
|
privateKey:
|
||||||
connection.sshMode == 'keyFile' && connection.sshKeyfile ? await fs.readFile(connection.sshKeyfile) : undefined,
|
connection.sshMode == 'keyFile' && (connection.sshKeyfile || platformInfo?.defaultKeyfile)
|
||||||
|
? await fs.readFile(connection.sshKeyfile || platformInfo?.defaultKeyfile)
|
||||||
|
: undefined,
|
||||||
skipAutoPrivateKey: true,
|
skipAutoPrivateKey: true,
|
||||||
noReadline: true,
|
noReadline: true,
|
||||||
};
|
};
|
||||||
|
|||||||
16
packages/api/src/shell/dbModelToJson.js
Normal file
16
packages/api/src/shell/dbModelToJson.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const importDbModel = require('../utility/importDbModel');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
async function dbModelToJson({ modelFolder, outputFile, commonjs }) {
|
||||||
|
const dbInfo = await importDbModel(modelFolder);
|
||||||
|
|
||||||
|
const json = JSON.stringify(dbInfo, null, 2);
|
||||||
|
if (commonjs) {
|
||||||
|
fs.writeFileSync(outputFile, `module.exports = ${json};`);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
fs.writeFileSync(outputFile, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = dbModelToJson;
|
||||||
@@ -27,6 +27,8 @@ const loadDatabase = require('./loadDatabase');
|
|||||||
const generateModelSql = require('./generateModelSql');
|
const generateModelSql = require('./generateModelSql');
|
||||||
const modifyJsonLinesReader = require('./modifyJsonLinesReader');
|
const modifyJsonLinesReader = require('./modifyJsonLinesReader');
|
||||||
const dataDuplicator = require('./dataDuplicator');
|
const dataDuplicator = require('./dataDuplicator');
|
||||||
|
const dbModelToJson = require('./dbModelToJson');
|
||||||
|
const jsonToDbModel = require('./jsonToDbModel');
|
||||||
|
|
||||||
const dbgateApi = {
|
const dbgateApi = {
|
||||||
queryReader,
|
queryReader,
|
||||||
@@ -57,6 +59,8 @@ const dbgateApi = {
|
|||||||
generateModelSql,
|
generateModelSql,
|
||||||
modifyJsonLinesReader,
|
modifyJsonLinesReader,
|
||||||
dataDuplicator,
|
dataDuplicator,
|
||||||
|
dbModelToJson,
|
||||||
|
jsonToDbModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
requirePlugin.initializeDbgateApi(dbgateApi);
|
requirePlugin.initializeDbgateApi(dbgateApi);
|
||||||
|
|||||||
9
packages/api/src/shell/jsonToDbModel.js
Normal file
9
packages/api/src/shell/jsonToDbModel.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const exportDbModel = require('../utility/exportDbModel');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
async function jsonToDbModel({ modelFile, outputDir }) {
|
||||||
|
const dbInfo = JSON.parse(fs.readFileSync(modelFile, 'utf-8'));
|
||||||
|
await exportDbModel(dbInfo, outputDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = jsonToDbModel;
|
||||||
@@ -11,6 +11,7 @@ const loadedPlugins = {};
|
|||||||
const dbgateEnv = {
|
const dbgateEnv = {
|
||||||
dbgateApi: null,
|
dbgateApi: null,
|
||||||
nativeModules,
|
nativeModules,
|
||||||
|
platformInfo,
|
||||||
};
|
};
|
||||||
function requirePlugin(packageName, requiredPlugin = null) {
|
function requirePlugin(packageName, requiredPlugin = null) {
|
||||||
if (!packageName) throw new Error('Missing packageName in plugin');
|
if (!packageName) throw new Error('Missing packageName in plugin');
|
||||||
|
|||||||
7
packages/api/src/utility/checkLicense.js
Normal file
7
packages/api/src/utility/checkLicense.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
function checkLicense() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
checkLicense,
|
||||||
|
};
|
||||||
@@ -1,72 +1,81 @@
|
|||||||
const { compilePermissions, testPermission } = require('dbgate-tools');
|
const { compilePermissions, testPermission } = require('dbgate-tools');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const { getAuthProvider } = require('../auth/authProvider');
|
||||||
|
|
||||||
const userPermissions = {};
|
const cachedPermissions = {};
|
||||||
|
|
||||||
function hasPermission(tested, req) {
|
function hasPermission(tested, req) {
|
||||||
if (!req) {
|
if (!req) {
|
||||||
// request object not available, allow all
|
// request object not available, allow all
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const { user } = (req && req.auth) || {};
|
|
||||||
const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {};
|
|
||||||
const key = user || login || '';
|
|
||||||
const logins = getLogins();
|
|
||||||
|
|
||||||
if (!userPermissions[key]) {
|
const permissions = getAuthProvider().getCurrentPermissions(req);
|
||||||
if (logins) {
|
|
||||||
const login = logins.find(x => x.login == user);
|
if (!cachedPermissions[permissions]) {
|
||||||
userPermissions[key] = compilePermissions(login ? login.permissions : null);
|
cachedPermissions[permissions] = compilePermissions(permissions);
|
||||||
} else {
|
|
||||||
userPermissions[key] = compilePermissions(process.env.PERMISSIONS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return testPermission(tested, userPermissions[key]);
|
|
||||||
|
return testPermission(tested, cachedPermissions[permissions]);
|
||||||
|
|
||||||
|
// const { user } = (req && req.auth) || {};
|
||||||
|
// const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {};
|
||||||
|
// const key = user || login || '';
|
||||||
|
// const logins = getLogins();
|
||||||
|
|
||||||
|
// if (!userPermissions[key]) {
|
||||||
|
// if (logins) {
|
||||||
|
// const login = logins.find(x => x.login == user);
|
||||||
|
// userPermissions[key] = compilePermissions(login ? login.permissions : null);
|
||||||
|
// } else {
|
||||||
|
// userPermissions[key] = compilePermissions(process.env.PERMISSIONS);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return testPermission(tested, userPermissions[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let loginsCache = null;
|
// let loginsCache = null;
|
||||||
let loginsLoaded = false;
|
// let loginsLoaded = false;
|
||||||
|
|
||||||
function getLogins() {
|
// function getLogins() {
|
||||||
if (loginsLoaded) {
|
// if (loginsLoaded) {
|
||||||
return loginsCache;
|
// return loginsCache;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const res = [];
|
// const res = [];
|
||||||
if (process.env.LOGIN && process.env.PASSWORD) {
|
// if (process.env.LOGIN && process.env.PASSWORD) {
|
||||||
res.push({
|
// res.push({
|
||||||
login: process.env.LOGIN,
|
// login: process.env.LOGIN,
|
||||||
password: process.env.PASSWORD,
|
// password: process.env.PASSWORD,
|
||||||
permissions: process.env.PERMISSIONS,
|
// permissions: process.env.PERMISSIONS,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
if (process.env.LOGINS) {
|
// if (process.env.LOGINS) {
|
||||||
const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
|
// const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
|
||||||
for (const login of logins) {
|
// for (const login of logins) {
|
||||||
const password = process.env[`LOGIN_PASSWORD_${login}`];
|
// const password = process.env[`LOGIN_PASSWORD_${login}`];
|
||||||
const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
|
// const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
|
||||||
if (password) {
|
// if (password) {
|
||||||
res.push({
|
// res.push({
|
||||||
login,
|
// login,
|
||||||
password,
|
// password,
|
||||||
permissions,
|
// permissions,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// } else if (process.env.OAUTH_PERMISSIONS) {
|
||||||
else if (process.env.OAUTH_PERMISSIONS) {
|
// const login_permission_keys = Object.keys(process.env).filter(key => _.startsWith(key, 'LOGIN_PERMISSIONS_'));
|
||||||
const login_permission_keys = Object.keys(process.env).filter((key) => _.startsWith(key, 'LOGIN_PERMISSIONS_'))
|
// for (const permissions_key of login_permission_keys) {
|
||||||
for (const permissions_key of login_permission_keys) {
|
// const login = permissions_key.replace('LOGIN_PERMISSIONS_', '');
|
||||||
const login = permissions_key.replace('LOGIN_PERMISSIONS_', '');
|
// const permissions = process.env[permissions_key];
|
||||||
const permissions = process.env[permissions_key];
|
// userPermissions[login] = compilePermissions(permissions);
|
||||||
userPermissions[login] = compilePermissions(permissions);
|
// }
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
loginsCache = res.length > 0 ? res : null;
|
// loginsCache = res.length > 0 ? res : null;
|
||||||
loginsLoaded = true;
|
// loginsLoaded = true;
|
||||||
return loginsCache;
|
// return loginsCache;
|
||||||
}
|
// }
|
||||||
|
|
||||||
function connectionHasPermission(connection, req) {
|
function connectionHasPermission(connection, req) {
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
@@ -87,7 +96,6 @@ function testConnectionPermission(connection, req) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
hasPermission,
|
hasPermission,
|
||||||
getLogins,
|
|
||||||
connectionHasPermission,
|
connectionHasPermission,
|
||||||
testConnectionPermission,
|
testConnectionPermission,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const os = require('os');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const processArgs = require('./processArgs');
|
const processArgs = require('./processArgs');
|
||||||
const isElectron = require('is-electron');
|
const isElectron = require('is-electron');
|
||||||
|
const { checkLicense } = require('./checkLicense');
|
||||||
|
|
||||||
const platform = process.env.OS_OVERRIDE ? process.env.OS_OVERRIDE : process.platform;
|
const platform = process.env.OS_OVERRIDE ? process.env.OS_OVERRIDE : process.platform;
|
||||||
const isWindows = platform === 'win32';
|
const isWindows = platform === 'win32';
|
||||||
@@ -12,6 +13,8 @@ const isDocker = fs.existsSync('/home/dbgate-docker/public');
|
|||||||
const isDevMode = process.env.DEVMODE == '1';
|
const isDevMode = process.env.DEVMODE == '1';
|
||||||
const isNpmDist = !!global['IS_NPM_DIST'];
|
const isNpmDist = !!global['IS_NPM_DIST'];
|
||||||
const isForkedApi = processArgs.isForkedApi;
|
const isForkedApi = processArgs.isForkedApi;
|
||||||
|
const licenseError = checkLicense();
|
||||||
|
const isLicenseValid = licenseError == null;
|
||||||
|
|
||||||
// function moduleAvailable(name) {
|
// function moduleAvailable(name) {
|
||||||
// try {
|
// try {
|
||||||
@@ -30,6 +33,8 @@ const platformInfo = {
|
|||||||
isElectronBundle: isElectron() && !isDevMode,
|
isElectronBundle: isElectron() && !isDevMode,
|
||||||
isForkedApi,
|
isForkedApi,
|
||||||
isElectron: isElectron(),
|
isElectron: isElectron(),
|
||||||
|
isLicenseValid,
|
||||||
|
licenseError,
|
||||||
isDevMode,
|
isDevMode,
|
||||||
isNpmDist,
|
isNpmDist,
|
||||||
isSnap: process.env.ELECTRON_SNAP == 'true',
|
isSnap: process.env.ELECTRON_SNAP == 'true',
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ module.exports = {
|
|||||||
electronSender.send(message, data == null ? null : data);
|
electronSender.send(message, data == null ? null : data);
|
||||||
}
|
}
|
||||||
for (const strmid in sseResponses) {
|
for (const strmid in sseResponses) {
|
||||||
|
if (data?.strmid && data?.strmid != strmid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let skipThisStream = false;
|
let skipThisStream = false;
|
||||||
if (sseResponses[strmid].filter) {
|
if (sseResponses[strmid].filter) {
|
||||||
for (const key in sseResponses[strmid].filter) {
|
for (const key in sseResponses[strmid].filter) {
|
||||||
@@ -47,7 +50,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sseResponses[strmid].response?.write(
|
sseResponses[strmid].response?.write(
|
||||||
`event: ${message}\ndata: ${stableStringify(data == null ? null : data)}\n\n`
|
`event: ${message}\ndata: ${stableStringify(data == null ? null : _.omit(data, ['strmid']))}\n\n`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ module.exports = function useController(app, electron, route, controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (raw) {
|
if (raw) {
|
||||||
router[method](routeAction, controller[key]);
|
router[method](routeAction, (req, res) => controller[key](req, res));
|
||||||
} else {
|
} else {
|
||||||
router[method](routeAction, async (req, res) => {
|
router[method](routeAction, async (req, res) => {
|
||||||
// if (controller._init && !controller._init_called) {
|
// if (controller._init && !controller._init_called) {
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ var config = {
|
|||||||
],
|
],
|
||||||
externals: {
|
externals: {
|
||||||
'better-sqlite3': 'commonjs better-sqlite3',
|
'better-sqlite3': 'commonjs better-sqlite3',
|
||||||
|
'oracledb': 'commonjs oracledb',
|
||||||
|
'msnodesqlv8': 'commonjs msnodesqlv8',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface ChangeSetItem {
|
|||||||
document?: any;
|
document?: any;
|
||||||
condition?: { [column: string]: string };
|
condition?: { [column: string]: string };
|
||||||
fields?: { [column: string]: string };
|
fields?: { [column: string]: string };
|
||||||
|
insertIfNotExistsFields?: { [column: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChangeSetItemFields {
|
export interface ChangeSetItemFields {
|
||||||
@@ -229,13 +230,23 @@ export function batchUpdateChangeSet(
|
|||||||
return changeSet;
|
return changeSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractFields(item: ChangeSetItem, allowNulls = true): UpdateField[] {
|
function extractFields(item: ChangeSetItem, allowNulls = true, allowedDocumentColumns: string[] = []): UpdateField[] {
|
||||||
return _.keys(item.fields)
|
const allFields = {
|
||||||
.filter(targetColumn => allowNulls || item.fields[targetColumn] != null)
|
...item.fields,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const docField in item.document || {}) {
|
||||||
|
if (allowedDocumentColumns.includes(docField)) {
|
||||||
|
allFields[docField] = item.document[docField];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.keys(allFields)
|
||||||
|
.filter(targetColumn => allowNulls || allFields[targetColumn] != null)
|
||||||
.map(targetColumn => ({
|
.map(targetColumn => ({
|
||||||
targetColumn,
|
targetColumn,
|
||||||
exprType: 'value',
|
exprType: 'value',
|
||||||
value: item.fields[targetColumn],
|
value: allFields[targetColumn],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,17 +254,19 @@ function changeSetInsertToSql(
|
|||||||
item: ChangeSetItem,
|
item: ChangeSetItem,
|
||||||
dbinfo: DatabaseInfo = null
|
dbinfo: DatabaseInfo = null
|
||||||
): [AllowIdentityInsert, Insert, AllowIdentityInsert] {
|
): [AllowIdentityInsert, Insert, AllowIdentityInsert] {
|
||||||
const fields = extractFields(item, false);
|
const table = dbinfo?.tables?.find(x => x.schemaName == item.schemaName && x.pureName == item.pureName);
|
||||||
|
const fields = extractFields(
|
||||||
|
item,
|
||||||
|
false,
|
||||||
|
table?.columns?.map(x => x.columnName)
|
||||||
|
);
|
||||||
if (fields.length == 0) return null;
|
if (fields.length == 0) return null;
|
||||||
let autoInc = false;
|
let autoInc = false;
|
||||||
if (dbinfo) {
|
if (table) {
|
||||||
const table = dbinfo.tables.find(x => x.schemaName == item.schemaName && x.pureName == item.pureName);
|
const autoIncCol = table.columns.find(x => x.autoIncrement);
|
||||||
if (table) {
|
// console.log('autoIncCol', autoIncCol);
|
||||||
const autoIncCol = table.columns.find(x => x.autoIncrement);
|
if (autoIncCol && fields.find(x => x.targetColumn == autoIncCol.columnName)) {
|
||||||
// console.log('autoIncCol', autoIncCol);
|
autoInc = true;
|
||||||
if (autoIncCol && fields.find(x => x.targetColumn == autoIncCol.columnName)) {
|
|
||||||
autoInc = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const targetTable = {
|
const targetTable = {
|
||||||
@@ -272,6 +285,9 @@ function changeSetInsertToSql(
|
|||||||
targetTable,
|
targetTable,
|
||||||
commandType: 'insert',
|
commandType: 'insert',
|
||||||
fields,
|
fields,
|
||||||
|
insertWhereNotExistsCondition: item.insertIfNotExistsFields
|
||||||
|
? compileSimpleChangeSetCondition(item.insertIfNotExistsFields)
|
||||||
|
: null,
|
||||||
},
|
},
|
||||||
autoInc
|
autoInc
|
||||||
? {
|
? {
|
||||||
@@ -320,7 +336,39 @@ export function extractChangeSetCondition(item: ChangeSetItem, alias?: string):
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeSetUpdateToSql(item: ChangeSetItem): Update {
|
function compileSimpleChangeSetCondition(fields: { [column: string]: string }): Condition {
|
||||||
|
function getColumnCondition(columnName: string): Condition {
|
||||||
|
const value = fields[columnName];
|
||||||
|
const expr: Expression = {
|
||||||
|
exprType: 'column',
|
||||||
|
columnName,
|
||||||
|
};
|
||||||
|
if (value == null) {
|
||||||
|
return {
|
||||||
|
conditionType: 'isNull',
|
||||||
|
expr,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
conditionType: 'binary',
|
||||||
|
operator: '=',
|
||||||
|
left: expr,
|
||||||
|
right: {
|
||||||
|
exprType: 'value',
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
conditionType: 'and',
|
||||||
|
conditions: _.keys(fields).map(columnName => getColumnCondition(columnName)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeSetUpdateToSql(item: ChangeSetItem, dbinfo: DatabaseInfo = null): Update {
|
||||||
|
const table = dbinfo?.tables?.find(x => x.schemaName == item.schemaName && x.pureName == item.pureName);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
from: {
|
from: {
|
||||||
name: {
|
name: {
|
||||||
@@ -329,7 +377,11 @@ function changeSetUpdateToSql(item: ChangeSetItem): Update {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
commandType: 'update',
|
commandType: 'update',
|
||||||
fields: extractFields(item),
|
fields: extractFields(
|
||||||
|
item,
|
||||||
|
true,
|
||||||
|
table?.columns?.map(x => x.columnName)
|
||||||
|
),
|
||||||
where: extractChangeSetCondition(item),
|
where: extractChangeSetCondition(item),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -351,7 +403,7 @@ export function changeSetToSql(changeSet: ChangeSet, dbinfo: DatabaseInfo): Comm
|
|||||||
return _.compact(
|
return _.compact(
|
||||||
_.flatten([
|
_.flatten([
|
||||||
...(changeSet.inserts.map(item => changeSetInsertToSql(item, dbinfo)) as any),
|
...(changeSet.inserts.map(item => changeSetInsertToSql(item, dbinfo)) as any),
|
||||||
...changeSet.updates.map(changeSetUpdateToSql),
|
...changeSet.updates.map(item => changeSetUpdateToSql(item, dbinfo)),
|
||||||
...changeSet.deletes.map(changeSetDeleteToSql),
|
...changeSet.deletes.map(changeSetDeleteToSql),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
@@ -446,7 +498,12 @@ export function changeSetInsertNewRow(changeSet: ChangeSet, name?: NamedObjectIn
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeSetInsertDocuments(changeSet: ChangeSet, documents: any[], name?: NamedObjectInfo): ChangeSet {
|
export function changeSetInsertDocuments(
|
||||||
|
changeSet: ChangeSet,
|
||||||
|
documents: any[],
|
||||||
|
name?: NamedObjectInfo,
|
||||||
|
insertIfNotExistsFieldNames?: string[]
|
||||||
|
): ChangeSet {
|
||||||
const insertedRows = getChangeSetInsertedRows(changeSet, name);
|
const insertedRows = getChangeSetInsertedRows(changeSet, name);
|
||||||
return {
|
return {
|
||||||
...changeSet,
|
...changeSet,
|
||||||
@@ -456,6 +513,7 @@ export function changeSetInsertDocuments(changeSet: ChangeSet, documents: any[],
|
|||||||
...name,
|
...name,
|
||||||
insertedRowIndex: insertedRows.length + index,
|
insertedRowIndex: insertedRows.length + index,
|
||||||
fields: doc,
|
fields: doc,
|
||||||
|
insertIfNotExistsFields: insertIfNotExistsFieldNames ? _.pick(doc, insertIfNotExistsFieldNames) : null,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
91
packages/datalib/src/CustomGridDisplay.ts
Normal file
91
packages/datalib/src/CustomGridDisplay.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { filterName, isTableColumnUnique } from 'dbgate-tools';
|
||||||
|
import { GridDisplay, ChangeCacheFunc, DisplayColumn, DisplayedColumnInfo, ChangeConfigFunc } from './GridDisplay';
|
||||||
|
import type {
|
||||||
|
TableInfo,
|
||||||
|
EngineDriver,
|
||||||
|
ViewInfo,
|
||||||
|
ColumnInfo,
|
||||||
|
NamedObjectInfo,
|
||||||
|
DatabaseInfo,
|
||||||
|
ForeignKeyInfo,
|
||||||
|
} from 'dbgate-types';
|
||||||
|
import { GridConfig, GridCache, createGridCache } from './GridConfig';
|
||||||
|
import { Expression, Select, treeToSql, dumpSqlSelect, ColumnRefExpression, Condition } from 'dbgate-sqltree';
|
||||||
|
|
||||||
|
export interface CustomGridColumn {
|
||||||
|
columnName: string;
|
||||||
|
columnLabel: string;
|
||||||
|
isPrimaryKey?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomGridDisplay extends GridDisplay {
|
||||||
|
customColumns: CustomGridColumn[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public tableName: NamedObjectInfo,
|
||||||
|
columns: CustomGridColumn[],
|
||||||
|
driver: EngineDriver,
|
||||||
|
config: GridConfig,
|
||||||
|
setConfig: ChangeConfigFunc,
|
||||||
|
cache: GridCache,
|
||||||
|
setCache: ChangeCacheFunc,
|
||||||
|
dbinfo: DatabaseInfo,
|
||||||
|
serverVersion,
|
||||||
|
isReadOnly = false,
|
||||||
|
public additionalcondition: Condition = null
|
||||||
|
) {
|
||||||
|
super(config, setConfig, cache, setCache, driver, dbinfo, serverVersion);
|
||||||
|
|
||||||
|
this.customColumns = columns;
|
||||||
|
|
||||||
|
this.columns = columns.map(col => ({
|
||||||
|
columnName: col.columnName,
|
||||||
|
headerText: col.columnLabel,
|
||||||
|
uniqueName: col.columnName,
|
||||||
|
uniquePath: [col.columnName],
|
||||||
|
isPrimaryKey: col.isPrimaryKey,
|
||||||
|
isForeignKeyUnique: false,
|
||||||
|
schemaName: tableName.schemaName,
|
||||||
|
pureName: tableName.pureName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.changeSetKeyFields = columns.filter(x => x.isPrimaryKey).map(x => x.columnName);
|
||||||
|
this.baseTable = {
|
||||||
|
...tableName,
|
||||||
|
columns: this.columns.map(x => ({ ...tableName, columnName: x.columnName, dataType: 'string' })),
|
||||||
|
foreignKeys: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.filterable = true;
|
||||||
|
this.sortable = true;
|
||||||
|
this.groupable = false;
|
||||||
|
this.editable = !isReadOnly;
|
||||||
|
this.supportsReload = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
createSelect(options = {}) {
|
||||||
|
const select = this.createSelectBase(
|
||||||
|
this.tableName,
|
||||||
|
[],
|
||||||
|
// @ts-ignore
|
||||||
|
// this.columns.map(col => ({
|
||||||
|
// columnName: col.columnName,
|
||||||
|
// })),
|
||||||
|
options,
|
||||||
|
this.customColumns.find(x => x.isPrimaryKey)?.columnName
|
||||||
|
);
|
||||||
|
select.selectAll = true;
|
||||||
|
if (this.additionalcondition) {
|
||||||
|
if (select.where) {
|
||||||
|
select.where = {
|
||||||
|
conditionType: 'and',
|
||||||
|
conditions: [select.where, this.additionalcondition],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
select.where = this.additionalcondition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return select;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -556,9 +556,9 @@ export abstract class GridDisplay {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
createSelectBase(name: NamedObjectInfo, columns: ColumnInfo[], options) {
|
createSelectBase(name: NamedObjectInfo, columns: ColumnInfo[], options, defaultOrderColumnName?: string) {
|
||||||
if (!columns) return null;
|
if (!columns) return null;
|
||||||
const orderColumnName = columns[0].columnName;
|
const orderColumnName = defaultOrderColumnName ?? columns[0]?.columnName;
|
||||||
const select: Select = {
|
const select: Select = {
|
||||||
commandType: 'select',
|
commandType: 'select',
|
||||||
from: {
|
from: {
|
||||||
@@ -734,6 +734,7 @@ export abstract class GridDisplay {
|
|||||||
alias: 'count',
|
alias: 'count',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
select.selectAll = false;
|
||||||
}
|
}
|
||||||
return select;
|
return select;
|
||||||
// const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
// const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
||||||
|
|||||||
@@ -21,3 +21,4 @@ export * from './perspectiveTools';
|
|||||||
export * from './DataDuplicator';
|
export * from './DataDuplicator';
|
||||||
export * from './FreeTableGridDisplay';
|
export * from './FreeTableGridDisplay';
|
||||||
export * from './FreeTableModel';
|
export * from './FreeTableModel';
|
||||||
|
export * from './CustomGridDisplay';
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ async function runAndExit(promise) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
program.version(dbgateApi.currentVersion.version);
|
||||||
|
|
||||||
program
|
program
|
||||||
.option('-s, --server <server>', 'server host')
|
.option('-s, --server <server>', 'server host')
|
||||||
.option('-u, --user <user>', 'user name')
|
.option('-u, --user <user>', 'user name')
|
||||||
@@ -36,7 +38,8 @@ program
|
|||||||
'--load-data-condition <condition>',
|
'--load-data-condition <condition>',
|
||||||
'regex, which table data will be loaded and stored in model (in load command)'
|
'regex, which table data will be loaded and stored in model (in load command)'
|
||||||
)
|
)
|
||||||
.requiredOption('-e, --engine <engine>', 'engine name, eg. mysql@dbgate-plugin-mysql');
|
.option('-e, --engine <engine>', 'engine name, eg. mysql@dbgate-plugin-mysql')
|
||||||
|
.option('--commonjs', 'Creates CommonJS module');
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('deploy <modelFolder>')
|
.command('deploy <modelFolder>')
|
||||||
@@ -115,4 +118,30 @@ program
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('json-to-model <jsonFile> <modelFolder>')
|
||||||
|
.description('Converts JSON file to model')
|
||||||
|
.action((jsonFile, modelFolder) => {
|
||||||
|
runAndExit(
|
||||||
|
dbgateApi.jsonToDbModel({
|
||||||
|
modelFile: jsonFile,
|
||||||
|
outputDir: modelFolder,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('model-to-json <modelFolder> <jsonFile>')
|
||||||
|
.description('Converts model to JSON file')
|
||||||
|
.action((modelFolder, jsonFile) => {
|
||||||
|
const { commonjs } = program.opts();
|
||||||
|
runAndExit(
|
||||||
|
dbgateApi.dbModelToJson({
|
||||||
|
modelFolder,
|
||||||
|
outputFile: jsonFile,
|
||||||
|
commonjs,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
|||||||
if (cmd.selectAll) {
|
if (cmd.selectAll) {
|
||||||
dmp.put('* ');
|
dmp.put('* ');
|
||||||
}
|
}
|
||||||
if (cmd.columns) {
|
if (cmd.columns && cmd.columns.length > 0) {
|
||||||
if (cmd.selectAll) dmp.put('&n,');
|
if (cmd.selectAll) dmp.put('&n,');
|
||||||
dmp.put('&>&n');
|
dmp.put('&>&n');
|
||||||
dmp.putCollection(',&n', cmd.columns, fld => {
|
dmp.putCollection(',&n', cmd.columns, fld => {
|
||||||
@@ -92,13 +92,28 @@ export function dumpSqlDelete(dmp: SqlDumper, cmd: Delete) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function dumpSqlInsert(dmp: SqlDumper, cmd: Insert) {
|
export function dumpSqlInsert(dmp: SqlDumper, cmd: Insert) {
|
||||||
dmp.put(
|
if (cmd.insertWhereNotExistsCondition) {
|
||||||
'^insert ^into %f (%,i) ^values (',
|
dmp.put(
|
||||||
cmd.targetTable,
|
'^insert ^into %f (%,i) ^select ',
|
||||||
cmd.fields.map(x => x.targetColumn)
|
cmd.targetTable,
|
||||||
);
|
cmd.fields.map(x => x.targetColumn)
|
||||||
dmp.putCollection(',', cmd.fields, x => dumpSqlExpression(dmp, x));
|
);
|
||||||
dmp.put(')');
|
dmp.putCollection(',', cmd.fields, x => dumpSqlExpression(dmp, x));
|
||||||
|
if (dmp.dialect.requireFromDual) {
|
||||||
|
dmp.put(' ^from ^dual ');
|
||||||
|
}
|
||||||
|
dmp.put(' ^where ^not ^exists (^select * ^from %f ^where ', cmd.targetTable);
|
||||||
|
dumpSqlCondition(dmp, cmd.insertWhereNotExistsCondition);
|
||||||
|
dmp.put(')');
|
||||||
|
} else {
|
||||||
|
dmp.put(
|
||||||
|
'^insert ^into %f (%,i) ^values (',
|
||||||
|
cmd.targetTable,
|
||||||
|
cmd.fields.map(x => x.targetColumn)
|
||||||
|
);
|
||||||
|
dmp.putCollection(',', cmd.fields, x => dumpSqlExpression(dmp, x));
|
||||||
|
dmp.put(')');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dumpSqlCommand(dmp: SqlDumper, cmd: Command) {
|
export function dumpSqlCommand(dmp: SqlDumper, cmd: Command) {
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) {
|
|||||||
dumpSqlExpression(dmp, condition.expr);
|
dumpSqlExpression(dmp, condition.expr);
|
||||||
dmp.put(' ^in (%,v)', condition.values);
|
dmp.put(' ^in (%,v)', condition.values);
|
||||||
break;
|
break;
|
||||||
|
case 'notIn':
|
||||||
|
dumpSqlExpression(dmp, condition.expr);
|
||||||
|
dmp.put(' ^not ^in (%,v)', condition.values);
|
||||||
|
break;
|
||||||
case 'rawTemplate':
|
case 'rawTemplate':
|
||||||
let was = false;
|
let was = false;
|
||||||
for (const item of condition.templateSql.split('$$')) {
|
for (const item of condition.templateSql.split('$$')) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface Insert {
|
|||||||
commandType: 'insert';
|
commandType: 'insert';
|
||||||
fields: UpdateField[];
|
fields: UpdateField[];
|
||||||
targetTable: NamedObjectInfo;
|
targetTable: NamedObjectInfo;
|
||||||
|
insertWhereNotExistsCondition?: Condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AllowIdentityInsert {
|
export interface AllowIdentityInsert {
|
||||||
@@ -105,6 +106,12 @@ export interface InCondition {
|
|||||||
values: any[];
|
values: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NotInCondition {
|
||||||
|
conditionType: 'notIn';
|
||||||
|
expr: Expression;
|
||||||
|
values: any[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface RawTemplateCondition {
|
export interface RawTemplateCondition {
|
||||||
conditionType: 'rawTemplate';
|
conditionType: 'rawTemplate';
|
||||||
templateSql: string;
|
templateSql: string;
|
||||||
@@ -126,6 +133,7 @@ export type Condition =
|
|||||||
| NotExistsCondition
|
| NotExistsCondition
|
||||||
| BetweenCondition
|
| BetweenCondition
|
||||||
| InCondition
|
| InCondition
|
||||||
|
| NotInCondition
|
||||||
| RawTemplateCondition
|
| RawTemplateCondition
|
||||||
| AnyColumnPassEvalOnlyCondition;
|
| AnyColumnPassEvalOnlyCondition;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function getConnectionLabelCore(connection, { allowExplicitDatabase = true } = {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function getConnectionLabel(connection, { allowExplicitDatabase = true, showUnsaved = false } = {}) {
|
export function getConnectionLabel(connection, { allowExplicitDatabase = true, showUnsaved = false } = {}) {
|
||||||
const res = getConnectionLabelCore(connection, { allowExplicitDatabase });
|
const res = getConnectionLabelCore(connection, { allowExplicitDatabase });
|
||||||
|
|
||||||
if (res && showUnsaved && connection?.unsaved) {
|
if (res && showUnsaved && connection?.unsaved) {
|
||||||
@@ -20,3 +20,4 @@ export * from './computeDiffRows';
|
|||||||
export * from './preloadedRowsTools';
|
export * from './preloadedRowsTools';
|
||||||
export * from './ScriptWriter';
|
export * from './ScriptWriter';
|
||||||
export * from './getLogger';
|
export * from './getLogger';
|
||||||
|
export * from './getConnectionLabel';
|
||||||
|
|||||||
@@ -73,3 +73,44 @@ export function testPermission(tested: string, permissions: CompiledPermissions)
|
|||||||
|
|
||||||
return allow;
|
return allow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function testSubPermission(
|
||||||
|
tested: string,
|
||||||
|
permissions: string[],
|
||||||
|
allowSamePermission = true
|
||||||
|
): true | false | null {
|
||||||
|
let result = null;
|
||||||
|
for (const permWithSign of permissions) {
|
||||||
|
const perm = permWithSign.startsWith('~') ? permWithSign.substring(1) : permWithSign;
|
||||||
|
const deny = permWithSign.startsWith('~');
|
||||||
|
|
||||||
|
if (perm.endsWith('*')) {
|
||||||
|
const prefix = perm.substring(0, perm.length - 1);
|
||||||
|
if (tested.startsWith(prefix)) {
|
||||||
|
result = !deny;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (allowSamePermission && tested == perm) {
|
||||||
|
result = !deny;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPredefinedPermissions(predefinedRoleName: string) {
|
||||||
|
switch (predefinedRoleName) {
|
||||||
|
case 'superadmin':
|
||||||
|
return ['*', '~widgets/*', 'widgets/admin', 'widgets/database', '~all-connections'];
|
||||||
|
case 'logged-user':
|
||||||
|
return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections'];
|
||||||
|
case 'anonymous-user':
|
||||||
|
return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections'];
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortPermissionsFromTheSameLevel(permissions: string[]) {
|
||||||
|
return [...permissions.filter(x => x.startsWith('~')), ...permissions.filter(x => !x.startsWith('~'))];
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export interface ColumnInfoYaml {
|
|||||||
length?: number;
|
length?: number;
|
||||||
autoIncrement?: boolean;
|
autoIncrement?: boolean;
|
||||||
references?: string;
|
references?: string;
|
||||||
|
refDeleteAction?: string;
|
||||||
|
refUpdateAction?: string;
|
||||||
primaryKey?: boolean;
|
primaryKey?: boolean;
|
||||||
default?: string;
|
default?: string;
|
||||||
}
|
}
|
||||||
@@ -104,6 +106,8 @@ function convertForeignKeyFromYaml(
|
|||||||
constraintType: 'foreignKey',
|
constraintType: 'foreignKey',
|
||||||
pureName: table.name,
|
pureName: table.name,
|
||||||
refTableName: col.references,
|
refTableName: col.references,
|
||||||
|
deleteAction: col.refDeleteAction,
|
||||||
|
updateAction: col.refUpdateAction,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
columnName: col.name,
|
columnName: col.name,
|
||||||
@@ -134,11 +138,15 @@ export function tableInfoFromYaml(table: TableInfoYaml, allTables: TableInfoYaml
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function databaseInfoFromYamlModel(files: DatabaseModelFile[]): DatabaseInfo {
|
export function databaseInfoFromYamlModel(filesOrDbInfo: DatabaseModelFile[] | DatabaseInfo): DatabaseInfo {
|
||||||
|
if (!Array.isArray(filesOrDbInfo)) {
|
||||||
|
return filesOrDbInfo;
|
||||||
|
}
|
||||||
|
|
||||||
const model = DatabaseAnalyser.createEmptyStructure();
|
const model = DatabaseAnalyser.createEmptyStructure();
|
||||||
const tablesYaml = [];
|
const tablesYaml = [];
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of filesOrDbInfo) {
|
||||||
if (file.name.endsWith('.table.yaml') || file.name.endsWith('.sql')) {
|
if (file.name.endsWith('.table.yaml') || file.name.endsWith('.sql')) {
|
||||||
if (file.name.endsWith('.table.yaml')) {
|
if (file.name.endsWith('.table.yaml')) {
|
||||||
tablesYaml.push(file.json);
|
tablesYaml.push(file.json);
|
||||||
|
|||||||
1
packages/types/dialect.d.ts
vendored
1
packages/types/dialect.d.ts
vendored
@@ -34,6 +34,7 @@ export interface SqlDialect {
|
|||||||
dropCheck?: boolean;
|
dropCheck?: boolean;
|
||||||
|
|
||||||
dropReferencesWhenDropTable?: boolean;
|
dropReferencesWhenDropTable?: boolean;
|
||||||
|
requireFromDual?: boolean;
|
||||||
|
|
||||||
predefinedDataTypes: string[];
|
predefinedDataTypes: string[];
|
||||||
|
|
||||||
|
|||||||
10
packages/types/engines.d.ts
vendored
10
packages/types/engines.d.ts
vendored
@@ -90,7 +90,13 @@ export interface EngineDriver {
|
|||||||
profilerChartMeasures?: { label: string; field: string }[];
|
profilerChartMeasures?: { label: string; field: string }[];
|
||||||
isElectronOnly?: boolean;
|
isElectronOnly?: boolean;
|
||||||
supportedCreateDatabase?: boolean;
|
supportedCreateDatabase?: boolean;
|
||||||
showConnectionField?: (field: string, values: any) => boolean;
|
showConnectionField?: (
|
||||||
|
field: string,
|
||||||
|
values: any,
|
||||||
|
{
|
||||||
|
config: {},
|
||||||
|
}
|
||||||
|
) => boolean;
|
||||||
showConnectionTab?: (tab: 'ssl' | 'sshTunnel', values: any) => boolean;
|
showConnectionTab?: (tab: 'ssl' | 'sshTunnel', values: any) => boolean;
|
||||||
beforeConnectionSave?: (values: any) => any;
|
beforeConnectionSave?: (values: any) => any;
|
||||||
databaseUrlPlaceholder?: string;
|
databaseUrlPlaceholder?: string;
|
||||||
@@ -143,6 +149,8 @@ export interface EngineDriver {
|
|||||||
summaryCommand(pool, command, row): Promise<void>;
|
summaryCommand(pool, command, row): Promise<void>;
|
||||||
startProfiler(pool, options): Promise<any>;
|
startProfiler(pool, options): Promise<any>;
|
||||||
stopProfiler(pool, profiler): Promise<void>;
|
stopProfiler(pool, profiler): Promise<void>;
|
||||||
|
getRedirectAuthUrl(connection, options): Promise<string>;
|
||||||
|
getAuthTokenFromCode(connection, options): Promise<string>;
|
||||||
|
|
||||||
analyserClass?: any;
|
analyserClass?: any;
|
||||||
dumperClass?: any;
|
dumperClass?: any;
|
||||||
|
|||||||
@@ -14,13 +14,15 @@
|
|||||||
// import { shouldWaitForElectronInitialize } from './utility/getElectron';
|
// import { shouldWaitForElectronInitialize } from './utility/getElectron';
|
||||||
import { subscribeConnectionPingers } from './utility/connectionsPinger';
|
import { subscribeConnectionPingers } from './utility/connectionsPinger';
|
||||||
import { subscribePermissionCompiler } from './utility/hasPermission';
|
import { subscribePermissionCompiler } from './utility/hasPermission';
|
||||||
import { apiCall } from './utility/api';
|
import { apiCall, installNewVolatileConnectionListener } from './utility/api';
|
||||||
import { getConfig, getSettings, getUsedApps } from './utility/metadataLoaders';
|
import { getConfig, getSettings, getUsedApps } from './utility/metadataLoaders';
|
||||||
import AppTitleProvider from './utility/AppTitleProvider.svelte';
|
import AppTitleProvider from './utility/AppTitleProvider.svelte';
|
||||||
import getElectron from './utility/getElectron';
|
import getElectron from './utility/getElectron';
|
||||||
import AppStartInfo from './widgets/AppStartInfo.svelte';
|
import AppStartInfo from './widgets/AppStartInfo.svelte';
|
||||||
import SettingsListener from './utility/SettingsListener.svelte';
|
import SettingsListener from './utility/SettingsListener.svelte';
|
||||||
import { handleAuthOnStartup, handleOauthCallback } from './clientAuth';
|
import { handleAuthOnStartup } from './clientAuth';
|
||||||
|
|
||||||
|
export let isAdminPage = false;
|
||||||
|
|
||||||
let loadedApi = false;
|
let loadedApi = false;
|
||||||
let loadedPlugins = false;
|
let loadedPlugins = false;
|
||||||
@@ -35,19 +37,22 @@
|
|||||||
// console.log('************** LOADING API');
|
// console.log('************** LOADING API');
|
||||||
|
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
await handleAuthOnStartup(config);
|
await handleAuthOnStartup(config, isAdminPage);
|
||||||
|
|
||||||
const connections = await apiCall('connections/list');
|
const connections = await apiCall('connections/list');
|
||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
||||||
const apps = await getUsedApps();
|
const apps = await getUsedApps();
|
||||||
loadedApi = settings && connections && config && apps;
|
const loadedApiValue = !!(settings && connections && config && apps);
|
||||||
|
|
||||||
if (loadedApi) {
|
if (loadedApiValue) {
|
||||||
subscribeApiDependendStores();
|
subscribeApiDependendStores();
|
||||||
subscribeConnectionPingers();
|
subscribeConnectionPingers();
|
||||||
subscribePermissionCompiler();
|
subscribePermissionCompiler();
|
||||||
|
installNewVolatileConnectionListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadedApi = loadedApiValue;
|
||||||
|
|
||||||
if (!loadedApi) {
|
if (!loadedApi) {
|
||||||
console.log('API not initialized correctly, trying again in 1s');
|
console.log('API not initialized correctly, trying again in 1s');
|
||||||
setTimeout(loadApi, 1000);
|
setTimeout(loadApi, 1000);
|
||||||
|
|||||||
95
packages/web/src/ErrorPage.svelte
Normal file
95
packages/web/src/ErrorPage.svelte
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { useConfig } from './utility/metadataLoaders';
|
||||||
|
import ErrorInfo from './elements/ErrorInfo.svelte';
|
||||||
|
import Link from './elements/Link.svelte';
|
||||||
|
import { internalRedirectTo } from './clientAuth';
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const error = params.get('error');
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const removed = document.getElementById('starting_dbgate_zero');
|
||||||
|
if (removed) removed.remove();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root theme-light theme-type-light">
|
||||||
|
<div class="text">DbGate</div>
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="logo">
|
||||||
|
<img class="img" src="logo192.png" />
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="heading">Configuration error</div>
|
||||||
|
{#if $config?.isLicenseValid == false}
|
||||||
|
<ErrorInfo
|
||||||
|
message={`Invalid license. Please contact sales@dbgate.eu for more details. ${$config?.licenseError}`}
|
||||||
|
/>
|
||||||
|
{:else if error}
|
||||||
|
<ErrorInfo message={error} />
|
||||||
|
{:else}
|
||||||
|
<ErrorInfo message="No error found, try to open app again" />
|
||||||
|
<div class="m-2">
|
||||||
|
<Link onClick={() => internalRedirectTo('/')}>Back to app</Link>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
font-size: 30pt;
|
||||||
|
font-family: monospace;
|
||||||
|
color: var(--theme-bg-2);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
color: var(--theme-font-1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--theme-bg-1);
|
||||||
|
align-items: baseline;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 600px;
|
||||||
|
max-width: 80vw;
|
||||||
|
/* max-width: 600px;
|
||||||
|
width: 40vw; */
|
||||||
|
border: 1px solid var(--theme-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--theme-bg-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
margin-top: 20vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
text-align: center;
|
||||||
|
margin: 1em;
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,16 +1,46 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { internalRedirectTo } from './clientAuth';
|
import { internalRedirectTo } from './clientAuth';
|
||||||
import FormButton from './forms/FormButton.svelte';
|
|
||||||
import FormPasswordField from './forms/FormPasswordField.svelte';
|
import FormPasswordField from './forms/FormPasswordField.svelte';
|
||||||
import FormProvider from './forms/FormProvider.svelte';
|
|
||||||
import FormSubmit from './forms/FormSubmit.svelte';
|
import FormSubmit from './forms/FormSubmit.svelte';
|
||||||
import FormTextField from './forms/FormTextField.svelte';
|
import FormTextField from './forms/FormTextField.svelte';
|
||||||
import { apiCall, enableApi } from './utility/api';
|
import { apiCall, enableApi, strmid } from './utility/api';
|
||||||
|
import { useConfig } from './utility/metadataLoaders';
|
||||||
|
import ErrorInfo from './elements/ErrorInfo.svelte';
|
||||||
|
import FormSelectField from './forms/FormSelectField.svelte';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import FormProviderCore from './forms/FormProviderCore.svelte';
|
||||||
|
import { openWebLink } from './utility/exportFileTools';
|
||||||
|
import FontIcon from './icons/FontIcon.svelte';
|
||||||
|
import createRef from './utility/createRef';
|
||||||
|
|
||||||
|
export let isAdminPage;
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
let availableConnections = null;
|
||||||
|
let isTesting = false;
|
||||||
|
const testIdRef = createRef(0);
|
||||||
|
let sqlConnectResult;
|
||||||
|
|
||||||
|
const values = writable({ databaseServer: null });
|
||||||
|
|
||||||
|
$: selectedConnection = availableConnections?.find(x => x.conid == $values.databaseServer);
|
||||||
|
|
||||||
|
async function loadAvailableServers() {
|
||||||
|
availableConnections = await apiCall('storage/get-connections-for-login-page');
|
||||||
|
if (availableConnections?.length > 0) {
|
||||||
|
values.set({ databaseServer: availableConnections[0].conid });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const removed = document.getElementById('starting_dbgate_zero');
|
const removed = document.getElementById('starting_dbgate_zero');
|
||||||
if (removed) removed.remove();
|
if (removed) removed.remove();
|
||||||
|
|
||||||
|
if (!isAdminPage) {
|
||||||
|
loadAvailableServers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -22,31 +52,129 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="heading">Log In</div>
|
<div class="heading">Log In</div>
|
||||||
<FormProvider>
|
<FormProviderCore {values}>
|
||||||
<FormTextField label="Username" name="login" autocomplete="username" saveOnInput />
|
{#if !isAdminPage && availableConnections}
|
||||||
<FormPasswordField label="Password" name="password" autocomplete="current-password" saveOnInput />
|
<FormSelectField
|
||||||
|
label="Database server"
|
||||||
|
name="databaseServer"
|
||||||
|
isNative
|
||||||
|
options={availableConnections.map(conn => ({ value: conn.conid, label: conn.label }))}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if selectedConnection}
|
||||||
|
{#if selectedConnection.passwordMode == 'askUser'}
|
||||||
|
<FormTextField label="Username" name="login" autocomplete="username" saveOnInput />
|
||||||
|
{/if}
|
||||||
|
{#if selectedConnection.passwordMode == 'askUser' || selectedConnection.passwordMode == 'askPassword'}
|
||||||
|
<FormPasswordField label="Password" name="password" autocomplete="current-password" saveOnInput />
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
{#if !isAdminPage}
|
||||||
|
<FormTextField label="Username" name="login" autocomplete="username" saveOnInput />
|
||||||
|
{/if}
|
||||||
|
<FormPasswordField label="Password" name="password" autocomplete="current-password" saveOnInput />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if isAdminPage && $config && !$config.isAdminLoginForm}
|
||||||
|
<ErrorInfo message="Admin login is not configured. Please set ADMIN_PASSWORD environment variable" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if isTesting}
|
||||||
|
<div class="ml-5">
|
||||||
|
<FontIcon icon="icon loading" /> Testing connection
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error'}
|
||||||
|
<div class="error-result ml-5">
|
||||||
|
Connect failed: <FontIcon icon="img error" />
|
||||||
|
{sqlConnectResult.error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="submit">
|
<div class="submit">
|
||||||
<FormSubmit
|
{#if selectedConnection?.useRedirectDbLogin}
|
||||||
value="Log In"
|
<FormSubmit
|
||||||
on:click={async e => {
|
value="Open database login page"
|
||||||
enableApi();
|
on:click={async e => {
|
||||||
const resp = await apiCall('auth/login', e.detail);
|
const state = `dbg-dblogin:${strmid}:${selectedConnection?.conid}`;
|
||||||
if (resp.error) {
|
sessionStorage.setItem('dbloginAuthState', state);
|
||||||
internalRedirectTo(`/?page=not-logged&error=${encodeURIComponent(resp.error)}`);
|
// openWebLink(
|
||||||
return;
|
// `connections/dblogin?conid=${selectedConnection?.conid}&state=${encodeURIComponent(state)}&redirectUri=${
|
||||||
}
|
// location.origin + location.pathname
|
||||||
const { accessToken } = resp;
|
// }`
|
||||||
if (accessToken) {
|
// );
|
||||||
localStorage.setItem('accessToken', accessToken);
|
internalRedirectTo(
|
||||||
internalRedirectTo('/');
|
`/connections/dblogin?conid=${selectedConnection?.conid}&state=${encodeURIComponent(state)}&redirectUri=${
|
||||||
return;
|
location.origin + location.pathname
|
||||||
}
|
}`
|
||||||
internalRedirectTo(`/?page=not-logged`);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{:else if selectedConnection}
|
||||||
|
<FormSubmit
|
||||||
|
value="Log In"
|
||||||
|
on:click={async e => {
|
||||||
|
if (selectedConnection.passwordMode == 'askUser' || selectedConnection.passwordMode == 'askPassword') {
|
||||||
|
enableApi();
|
||||||
|
isTesting = true;
|
||||||
|
testIdRef.update(x => x + 1);
|
||||||
|
const testid = testIdRef.get();
|
||||||
|
const resp = await apiCall('connections/dblogin-auth', {
|
||||||
|
conid: selectedConnection.conid,
|
||||||
|
user: $values['login'],
|
||||||
|
password: $values['password'],
|
||||||
|
});
|
||||||
|
if (testIdRef.get() != testid) return;
|
||||||
|
isTesting = false;
|
||||||
|
if (resp.accessToken) {
|
||||||
|
localStorage.setItem('accessToken', resp.accessToken);
|
||||||
|
internalRedirectTo('/');
|
||||||
|
} else {
|
||||||
|
sqlConnectResult = resp;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enableApi();
|
||||||
|
const resp = await apiCall('connections/dblogin-auth', {
|
||||||
|
conid: selectedConnection.conid,
|
||||||
|
});
|
||||||
|
localStorage.setItem('accessToken', resp.accessToken);
|
||||||
|
internalRedirectTo('/');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<FormSubmit
|
||||||
|
value={isAdminPage ? 'Log In as Administrator' : 'Log In'}
|
||||||
|
on:click={async e => {
|
||||||
|
enableApi();
|
||||||
|
const resp = await apiCall('auth/login', {
|
||||||
|
isAdminPage,
|
||||||
|
...e.detail,
|
||||||
|
});
|
||||||
|
if (resp.error) {
|
||||||
|
internalRedirectTo(
|
||||||
|
`/?page=not-logged&error=${encodeURIComponent(resp.error)}&is-admin=${isAdminPage ? 'true' : ''}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { accessToken } = resp;
|
||||||
|
if (accessToken) {
|
||||||
|
localStorage.setItem(isAdminPage ? 'adminAccessToken' : 'accessToken', accessToken);
|
||||||
|
if (isAdminPage) {
|
||||||
|
internalRedirectTo('/?page=admin');
|
||||||
|
} else {
|
||||||
|
internalRedirectTo('/');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internalRedirectTo(`/?page=not-logged`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</FormProvider>
|
</FormProviderCore>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import FormStyledButton from './buttons/FormStyledButton.svelte';
|
import FormStyledButton from './buttons/FormStyledButton.svelte';
|
||||||
import { doLogout, redirectToLogin } from './clientAuth';
|
import { doLogout, redirectToAdminLogin, redirectToLogin } from './clientAuth';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const removed = document.getElementById('starting_dbgate_zero');
|
const removed = document.getElementById('starting_dbgate_zero');
|
||||||
@@ -10,9 +10,14 @@
|
|||||||
|
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const error = params.get('error');
|
const error = params.get('error');
|
||||||
|
const isAdmin = params.get('is-admin') == 'true';
|
||||||
|
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
redirectToLogin(undefined, true);
|
if (isAdmin) {
|
||||||
|
redirectToAdminLogin();
|
||||||
|
} else {
|
||||||
|
redirectToLogin(undefined, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,6 @@
|
|||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
|
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
|
||||||
import getElectron from '../utility/getElectron';
|
import getElectron from '../utility/getElectron';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
|
import { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
|
||||||
import { getLocalStorage } from '../utility/storageCache';
|
import { getLocalStorage } from '../utility/storageCache';
|
||||||
import { apiCall, removeVolatileMapping } from '../utility/api';
|
import { apiCall, removeVolatileMapping } from '../utility/api';
|
||||||
@@ -106,6 +105,7 @@
|
|||||||
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
||||||
import AboutModal from '../modals/AboutModal.svelte';
|
import AboutModal from '../modals/AboutModal.svelte';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
export let passProps;
|
export let passProps;
|
||||||
|
|||||||
@@ -340,7 +340,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
|
|
||||||
import _, { find } from 'lodash';
|
import _, { find } from 'lodash';
|
||||||
@@ -365,7 +364,7 @@
|
|||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
import AppObjectCore from './AppObjectCore.svelte';
|
import AppObjectCore from './AppObjectCore.svelte';
|
||||||
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
|
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
|
||||||
import { findEngineDriver } from 'dbgate-tools';
|
import { findEngineDriver, getConnectionLabel } from 'dbgate-tools';
|
||||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||||
import { getDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
|
import { getDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
|
||||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||||
|
|||||||
@@ -776,7 +776,7 @@
|
|||||||
pinnedTables,
|
pinnedTables,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
import { filterName, generateDbPairingId, getAlterDatabaseScript } from 'dbgate-tools';
|
import { filterName, generateDbPairingId, getAlterDatabaseScript, getConnectionLabel } from 'dbgate-tools';
|
||||||
import { getConnectionInfo, getDatabaseInfo } from '../utility/metadataLoaders';
|
import { getConnectionInfo, getDatabaseInfo } from '../utility/metadataLoaders';
|
||||||
import fullDisplayName from '../utility/fullDisplayName';
|
import fullDisplayName from '../utility/fullDisplayName';
|
||||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||||
@@ -784,7 +784,6 @@
|
|||||||
import { findEngineDriver } from 'dbgate-tools';
|
import { findEngineDriver } from 'dbgate-tools';
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import { exportQuickExportFile } from '../utility/exportFileTools';
|
import { exportQuickExportFile } from '../utility/exportFileTools';
|
||||||
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||||
import ConfirmSqlModal, { saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
|
import ConfirmSqlModal, { saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import { filterName } from 'dbgate-tools';
|
import { filterName, getConnectionLabel } from 'dbgate-tools';
|
||||||
|
|
||||||
interface FileTypeHandler {
|
interface FileTypeHandler {
|
||||||
icon: string;
|
icon: string;
|
||||||
@@ -100,7 +100,6 @@
|
|||||||
import { currentDatabase } from '../stores';
|
import { currentDatabase } from '../stores';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import hasPermission from '../utility/hasPermission';
|
import hasPermission from '../utility/hasPermission';
|
||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { useDatabaseList } from '../utility/metadataLoaders';
|
import { useDatabaseList } from '../utility/metadataLoaders';
|
||||||
import AppObjectList from './AppObjectList.svelte';
|
import AppObjectList from './AppObjectList.svelte';
|
||||||
import * as databaseAppObject from './DatabaseAppObject.svelte';
|
import * as databaseAppObject from './DatabaseAppObject.svelte';
|
||||||
|
import { volatileConnectionMapStore } from '../utility/api';
|
||||||
|
|
||||||
export let filter;
|
export let filter;
|
||||||
export let data;
|
export let data;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolstrip {
|
.toolstrip {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { apiCall, enableApi } from './utility/api';
|
import { apiCall, enableApi } from './utility/api';
|
||||||
import { getConfig } from './utility/metadataLoaders';
|
import { getConfig } from './utility/metadataLoaders';
|
||||||
|
import { isAdminPage } from './utility/pageDefs';
|
||||||
|
|
||||||
export function isOauthCallback() {
|
export function isOauthCallback() {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
@@ -11,6 +12,29 @@ export function isOauthCallback() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isDbLoginCallback() {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const sentCode = params.get('code');
|
||||||
|
const sentState = params.get('state');
|
||||||
|
|
||||||
|
return (
|
||||||
|
sentCode && sentState && sentState.startsWith('dbg-dblogin:') && sentState == localStorage.getItem('dbloginState')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDbLoginAuthCallback() {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const sentCode = params.get('code');
|
||||||
|
const sentState = params.get('state');
|
||||||
|
|
||||||
|
return (
|
||||||
|
sentCode &&
|
||||||
|
sentState &&
|
||||||
|
sentState.startsWith('dbg-dblogin:') &&
|
||||||
|
sentState == sessionStorage.getItem('dbloginAuthState')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function handleOauthCallback() {
|
export function handleOauthCallback() {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const sentCode = params.get('code');
|
const sentCode = params.get('code');
|
||||||
@@ -36,10 +60,68 @@ export function handleOauthCallback() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDbLoginCallback()) {
|
||||||
|
const [_prefix, strmid, conid] = localStorage.getItem('dbloginState').split(':');
|
||||||
|
localStorage.removeItem('dbloginState');
|
||||||
|
|
||||||
|
apiCall('connections/dblogin-token', {
|
||||||
|
code: sentCode,
|
||||||
|
conid,
|
||||||
|
strmid,
|
||||||
|
redirectUri: location.origin + location.pathname,
|
||||||
|
}).then(authResp => {
|
||||||
|
if (authResp.success) {
|
||||||
|
window.close();
|
||||||
|
} else if (authResp.error) {
|
||||||
|
internalRedirectTo(`/?page=error&error=${encodeURIComponent(authResp.error)}`);
|
||||||
|
} else {
|
||||||
|
internalRedirectTo(`/?page=error`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDbLoginAuthCallback()) {
|
||||||
|
const [_prefix, strmid, conid] = sessionStorage.getItem('dbloginAuthState').split(':');
|
||||||
|
sessionStorage.removeItem('dbloginAuthState');
|
||||||
|
|
||||||
|
apiCall('connections/dblogin-auth-token', {
|
||||||
|
code: sentCode,
|
||||||
|
conid,
|
||||||
|
redirectUri: location.origin + location.pathname,
|
||||||
|
}).then(authResp => {
|
||||||
|
if (authResp.accessToken) {
|
||||||
|
localStorage.setItem('accessToken', authResp.accessToken);
|
||||||
|
internalRedirectTo('/');
|
||||||
|
} else if (authResp.error) {
|
||||||
|
internalRedirectTo(`/?page=error&error=${encodeURIComponent(authResp.error)}`);
|
||||||
|
} else {
|
||||||
|
internalRedirectTo(`/?page=error`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleAuthOnStartup(config) {
|
export async function handleAuthOnStartup(config, isAdminPage = false) {
|
||||||
|
if (!config.isLicenseValid) {
|
||||||
|
internalRedirectTo(`/?page=error`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isAdminLoginForm && isAdminPage) {
|
||||||
|
if (localStorage.getItem('adminAccessToken')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectToAdminLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.oauth) {
|
if (config.oauth) {
|
||||||
console.log('OAUTH callback URL:', location.origin + location.pathname);
|
console.log('OAUTH callback URL:', location.origin + location.pathname);
|
||||||
}
|
}
|
||||||
@@ -52,6 +134,11 @@ export async function handleAuthOnStartup(config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function redirectToAdminLogin() {
|
||||||
|
internalRedirectTo('/?page=admin-login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
export async function redirectToLogin(config = null, force = false) {
|
export async function redirectToLogin(config = null, force = false) {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
enableApi();
|
enableApi();
|
||||||
@@ -61,7 +148,7 @@ export async function redirectToLogin(config = null, force = false) {
|
|||||||
if (config.isLoginForm) {
|
if (config.isLoginForm) {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
if (params.get('page') == 'login' || params.get('page') == 'not-logged') {
|
if (params.get('page') == 'login' || params.get('page') == 'admin-login' || params.get('page') == 'not-logged') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,15 +180,18 @@ export async function doLogout() {
|
|||||||
enableApi();
|
enableApi();
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
if (config.oauth) {
|
if (config.oauth) {
|
||||||
localStorage.removeItem('accessToken');
|
localStorage.removeItem(isAdminPage() ? 'adminAccessToken' : 'accessToken');
|
||||||
if (config.oauthLogout) {
|
if (config.oauthLogout) {
|
||||||
window.location.href = config.oauthLogout;
|
window.location.href = config.oauthLogout;
|
||||||
} else {
|
} else {
|
||||||
internalRedirectTo('/?page=not-logged');
|
internalRedirectTo('/?page=not-logged');
|
||||||
}
|
}
|
||||||
} else if (config.isLoginForm) {
|
} else if (config.isLoginForm) {
|
||||||
localStorage.removeItem('accessToken');
|
localStorage.removeItem(isAdminPage() ? 'adminAccessToken' : 'accessToken');
|
||||||
internalRedirectTo('/?page=not-logged');
|
internalRedirectTo(`/?page=not-logged&is-admin=${isAdminPage() ? 'true' : ''}`);
|
||||||
|
} else if (config.isAdminLoginForm && isAdminPage()) {
|
||||||
|
localStorage.removeItem('adminAccessToken');
|
||||||
|
internalRedirectTo('/?page=admin-login&is-admin=true');
|
||||||
} else {
|
} else {
|
||||||
window.location.href = 'config/logout';
|
window.location.href = 'config/logout';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { filterName } from 'dbgate-tools';
|
import { filterName, getConnectionLabel } from 'dbgate-tools';
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@@ -75,7 +75,6 @@
|
|||||||
visibleCommandPalette,
|
visibleCommandPalette,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import clickOutside from '../utility/clickOutside';
|
import clickOutside from '../utility/clickOutside';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import { isElectronAvailable } from '../utility/getElectron';
|
import { isElectronAvailable } from '../utility/getElectron';
|
||||||
import keycodes from '../utility/keycodes';
|
import keycodes from '../utility/keycodes';
|
||||||
import { useConnectionList, useDatabaseInfo } from '../utility/metadataLoaders';
|
import { useConnectionList, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { recentDatabases, currentDatabase, getRecentDatabases } from '../stores';
|
import { recentDatabases, currentDatabase, getRecentDatabases } from '../stores';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import registerCommand from './registerCommand';
|
import registerCommand from './registerCommand';
|
||||||
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
|
|
||||||
currentDatabase.subscribe(value => {
|
currentDatabase.subscribe(value => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ registerCommand({
|
|||||||
category: 'New',
|
category: 'New',
|
||||||
toolbarOrder: 1,
|
toolbarOrder: 1,
|
||||||
name: 'Connection',
|
name: 'Connection',
|
||||||
testEnabled: () => !getCurrentConfig()?.runAsPortal,
|
testEnabled: () => !getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
openNewTab({
|
openNewTab({
|
||||||
title: 'New Connection',
|
title: 'New Connection',
|
||||||
@@ -121,7 +121,7 @@ registerCommand({
|
|||||||
toolbarName: 'Add connection folder',
|
toolbarName: 'Add connection folder',
|
||||||
category: 'New',
|
category: 'New',
|
||||||
toolbarOrder: 1,
|
toolbarOrder: 1,
|
||||||
name: 'Connection',
|
name: 'Connection folder',
|
||||||
testEnabled: () => !getCurrentConfig()?.runAsPortal,
|
testEnabled: () => !getCurrentConfig()?.runAsPortal,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
showModal(InputTextModal, {
|
showModal(InputTextModal, {
|
||||||
@@ -551,7 +551,7 @@ registerCommand({
|
|||||||
id: 'app.logout',
|
id: 'app.logout',
|
||||||
category: 'App',
|
category: 'App',
|
||||||
name: 'Logout',
|
name: 'Logout',
|
||||||
testEnabled: () => getCurrentConfig()?.login != null,
|
testEnabled: () => getCurrentConfig()?.isUserLoggedIn,
|
||||||
onClick: doLogout,
|
onClick: doLogout,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -559,7 +559,7 @@ registerCommand({
|
|||||||
id: 'app.disconnect',
|
id: 'app.disconnect',
|
||||||
category: 'App',
|
category: 'App',
|
||||||
name: 'Disconnect',
|
name: 'Disconnect',
|
||||||
testEnabled: () => getCurrentConfig()?.singleConnection != null,
|
testEnabled: () => getCurrentConfig()?.singleConnection != null && !getCurrentConfig()?.isUserLoggedIn,
|
||||||
onClick: () => disconnectServerConnection(getCurrentConfig()?.singleConnection?._id),
|
onClick: () => disconnectServerConnection(getCurrentConfig()?.singleConnection?._id),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -873,7 +873,6 @@ registerCommand({
|
|||||||
onClick: () => showModal(UploadErrorModal),
|
onClick: () => showModal(UploadErrorModal),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
if (electron) {
|
if (electron) {
|
||||||
electron.addEventListener('run-command', (e, commandId) => runCommand(commandId));
|
electron.addEventListener('run-command', (e, commandId) => runCommand(commandId));
|
||||||
|
|||||||
@@ -135,12 +135,13 @@
|
|||||||
|
|
||||||
export let macroPreview;
|
export let macroPreview;
|
||||||
export let macroValues;
|
export let macroValues;
|
||||||
export let selectedCellsPublished;
|
|
||||||
export let setLoadedRows = null;
|
export let setLoadedRows = null;
|
||||||
|
export let onPublishedCellsChanged;
|
||||||
|
|
||||||
// export let onChangeGrider = undefined;
|
// export let onChangeGrider = undefined;
|
||||||
|
|
||||||
let loadedRows = [];
|
let loadedRows = [];
|
||||||
|
let publishedCells = [];
|
||||||
|
|
||||||
export const activator = createActivator('CollectionDataGridCore', false);
|
export const activator = createActivator('CollectionDataGridCore', false);
|
||||||
|
|
||||||
@@ -152,7 +153,7 @@
|
|||||||
display,
|
display,
|
||||||
macroPreview,
|
macroPreview,
|
||||||
macroValues,
|
macroValues,
|
||||||
selectedCellsPublished()
|
publishedCells
|
||||||
);
|
);
|
||||||
// $: console.log('GRIDER', grider);
|
// $: console.log('GRIDER', grider);
|
||||||
// $: if (onChangeGrider) onChangeGrider(grider);
|
// $: if (onChangeGrider) onChangeGrider(grider);
|
||||||
@@ -239,7 +240,12 @@
|
|||||||
{dataPageAvailable}
|
{dataPageAvailable}
|
||||||
{loadRowCount}
|
{loadRowCount}
|
||||||
setLoadedRows={handleSetLoadedRows}
|
setLoadedRows={handleSetLoadedRows}
|
||||||
bind:selectedCellsPublished
|
onPublishedCellsChanged={value => {
|
||||||
|
publishedCells = value;
|
||||||
|
if (onPublishedCellsChanged) {
|
||||||
|
onPublishedCellsChanged(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
frameSelection={!!macroPreview}
|
frameSelection={!!macroPreview}
|
||||||
onOpenQuery={openQuery}
|
onOpenQuery={openQuery}
|
||||||
{grider}
|
{grider}
|
||||||
|
|||||||
@@ -89,12 +89,15 @@
|
|||||||
export let onRunMacro;
|
export let onRunMacro;
|
||||||
export let hasMultiColumnFilter = false;
|
export let hasMultiColumnFilter = false;
|
||||||
export let setLoadedRows = null;
|
export let setLoadedRows = null;
|
||||||
|
export let hideGridLeftColumn = false;
|
||||||
|
|
||||||
|
export let onPublishedCellsChanged;
|
||||||
|
|
||||||
let loadedRows;
|
let loadedRows;
|
||||||
|
let publishedCells = [];
|
||||||
|
|
||||||
export const activator = createActivator('DataGrid', false);
|
export const activator = createActivator('DataGrid', false);
|
||||||
|
|
||||||
let selectedCellsPublished = () => [];
|
|
||||||
let domColumnManager;
|
let domColumnManager;
|
||||||
|
|
||||||
const selectedMacro = writable(null);
|
const selectedMacro = writable(null);
|
||||||
@@ -110,7 +113,7 @@
|
|||||||
$: isJsonView = !!config?.isJsonView;
|
$: isJsonView = !!config?.isJsonView;
|
||||||
|
|
||||||
const handleExecuteMacro = () => {
|
const handleExecuteMacro = () => {
|
||||||
onRunMacro($selectedMacro, extractMacroValuesForMacro($macroValues, $selectedMacro), selectedCellsPublished());
|
onRunMacro($selectedMacro, extractMacroValuesForMacro($macroValues, $selectedMacro), publishedCells);
|
||||||
$selectedMacro = null;
|
$selectedMacro = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,7 +125,7 @@
|
|||||||
|
|
||||||
export function switchToView(view) {
|
export function switchToView(view) {
|
||||||
if (view == 'form') {
|
if (view == 'form') {
|
||||||
display.switchToFormView(selectedCellsPublished()[0]?.row);
|
display.switchToFormView(publishedCells[0]?.row);
|
||||||
}
|
}
|
||||||
if (view == 'table') {
|
if (view == 'table') {
|
||||||
setConfig(cfg => ({
|
setConfig(cfg => ({
|
||||||
@@ -162,7 +165,7 @@
|
|||||||
<HorizontalSplitter
|
<HorizontalSplitter
|
||||||
initialValue={getInitialManagerSize()}
|
initialValue={getInitialManagerSize()}
|
||||||
bind:size={managerSize}
|
bind:size={managerSize}
|
||||||
hideFirst={$collapsedLeftColumnStore}
|
hideFirst={hideGridLeftColumn || $collapsedLeftColumnStore}
|
||||||
>
|
>
|
||||||
<div class="left" slot="1">
|
<div class="left" slot="1">
|
||||||
<WidgetColumnBar>
|
<WidgetColumnBar>
|
||||||
@@ -219,7 +222,12 @@
|
|||||||
macroValues={extractMacroValuesForMacro($macroValues, $selectedMacro)}
|
macroValues={extractMacroValuesForMacro($macroValues, $selectedMacro)}
|
||||||
macroPreview={$selectedMacro}
|
macroPreview={$selectedMacro}
|
||||||
{setLoadedRows}
|
{setLoadedRows}
|
||||||
bind:selectedCellsPublished
|
onPublishedCellsChanged={value => {
|
||||||
|
publishedCells = value;
|
||||||
|
if (onPublishedCellsChanged) {
|
||||||
|
onPublishedCellsChanged(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
onChangeSelectedColumns={cols => {
|
onChangeSelectedColumns={cols => {
|
||||||
if (domColumnManager) domColumnManager.setSelectedColumns(cols);
|
if (domColumnManager) domColumnManager.setSelectedColumns(cols);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -412,6 +412,7 @@
|
|||||||
export let isLoading = false;
|
export let isLoading = false;
|
||||||
export let allRowCount = undefined;
|
export let allRowCount = undefined;
|
||||||
export let onReferenceSourceChanged = undefined;
|
export let onReferenceSourceChanged = undefined;
|
||||||
|
export let onPublishedCellsChanged = undefined;
|
||||||
export let onReferenceClick = undefined;
|
export let onReferenceClick = undefined;
|
||||||
export let onChangeSelectedColumns = undefined;
|
export let onChangeSelectedColumns = undefined;
|
||||||
// export let onSelectedCellsPublishedChanged = undefined;
|
// export let onSelectedCellsPublishedChanged = undefined;
|
||||||
@@ -422,12 +423,13 @@
|
|||||||
export let schemaName = undefined;
|
export let schemaName = undefined;
|
||||||
export let allowDefineVirtualReferences = false;
|
export let allowDefineVirtualReferences = false;
|
||||||
export let formatterFunction;
|
export let formatterFunction;
|
||||||
|
export let hideGridLeftColumn;
|
||||||
|
|
||||||
export let isLoadedAll;
|
export let isLoadedAll;
|
||||||
export let loadedTime;
|
export let loadedTime;
|
||||||
export let changeSetStore;
|
export let changeSetStore;
|
||||||
export let isDynamicStructure = false;
|
export let isDynamicStructure = false;
|
||||||
export let selectedCellsPublished = () => [];
|
// export let selectedCellsPublished = () => [];
|
||||||
export let collapsedLeftColumnStore;
|
export let collapsedLeftColumnStore;
|
||||||
export let multipleGridsOnTab = false;
|
export let multipleGridsOnTab = false;
|
||||||
export let tabControlHiddenTab = false;
|
export let tabControlHiddenTab = false;
|
||||||
@@ -1077,16 +1079,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lastPublishledSelectedCellsRef = createRef('');
|
const lastPublishledSelectedCellsRef = createRef('');
|
||||||
|
const changeSetValueRef = createRef(null);
|
||||||
$: {
|
$: {
|
||||||
const stringified = stableStringify(selectedCells);
|
const stringified = stableStringify(selectedCells);
|
||||||
if (lastPublishledSelectedCellsRef.get() != stringified) {
|
if (
|
||||||
lastPublishledSelectedCellsRef.set(stringified);
|
(lastPublishledSelectedCellsRef.get() != stringified || changeSetValueRef.get() != $changeSetStore?.value) &&
|
||||||
const cellsValue = () => getCellsPublished(selectedCells);
|
realColumnUniqueNames?.length > 0
|
||||||
selectedCellsPublished = cellsValue;
|
) {
|
||||||
$selectedCellsCallback = cellsValue;
|
tick().then(() => {
|
||||||
|
const rowIndexes = _.uniq(selectedCells.map(x => x[0]));
|
||||||
|
if (rowIndexes.every(x => grider.getRowData(x))) {
|
||||||
|
lastPublishledSelectedCellsRef.set(stringified);
|
||||||
|
changeSetValueRef.set($changeSetStore?.value);
|
||||||
|
$selectedCellsCallback = () => getCellsPublished(selectedCells);
|
||||||
|
|
||||||
if (onChangeSelectedColumns) onChangeSelectedColumns(getSelectedColumns().map(x => x.columnName));
|
if (onChangeSelectedColumns) {
|
||||||
// if (onSelectedCellsPublishedChanged) onSelectedCellsPublishedChanged(getCellsPublished(selectedCells));
|
onChangeSelectedColumns(getSelectedColumns().map(x => x.columnName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onPublishedCellsChanged) {
|
||||||
|
onPublishedCellsChanged(getCellsPublished(selectedCells));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1120,6 +1135,8 @@
|
|||||||
column,
|
column,
|
||||||
value: rowData && rowData[column],
|
value: rowData && rowData[column],
|
||||||
engine: display?.driver,
|
engine: display?.driver,
|
||||||
|
condition: display?.getChangeSetCondition(rowData),
|
||||||
|
insertedRowIndex: grider?.getInsertedRowIndex(row),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(x => x.column);
|
.filter(x => x.column);
|
||||||
@@ -1816,10 +1833,12 @@
|
|||||||
data-col="header"
|
data-col="header"
|
||||||
style={`width:${headerColWidth}px; min-width:${headerColWidth}px; max-width:${headerColWidth}px`}
|
style={`width:${headerColWidth}px; min-width:${headerColWidth}px; max-width:${headerColWidth}px`}
|
||||||
>
|
>
|
||||||
<CollapseButton
|
{#if !hideGridLeftColumn}
|
||||||
collapsed={$collapsedLeftColumnStore}
|
<CollapseButton
|
||||||
on:click={() => collapsedLeftColumnStore.update(x => !x)}
|
collapsed={$collapsedLeftColumnStore}
|
||||||
/>
|
on:click={() => collapsedLeftColumnStore.update(x => !x)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
{#each visibleRealColumns as col (col.uniqueName)}
|
{#each visibleRealColumns as col (col.uniqueName)}
|
||||||
<td
|
<td
|
||||||
|
|||||||
@@ -69,11 +69,13 @@
|
|||||||
|
|
||||||
export let macroPreview;
|
export let macroPreview;
|
||||||
export let macroValues;
|
export let macroValues;
|
||||||
export let selectedCellsPublished = () => [];
|
export let onPublishedCellsChanged
|
||||||
export const activator = createActivator('JslDataGridCore', false);
|
export const activator = createActivator('JslDataGridCore', false);
|
||||||
|
|
||||||
export let setLoadedRows;
|
export let setLoadedRows;
|
||||||
|
|
||||||
|
let publishedCells = [];
|
||||||
|
|
||||||
let loadedRows = [];
|
let loadedRows = [];
|
||||||
let domGrid;
|
let domGrid;
|
||||||
|
|
||||||
@@ -113,7 +115,7 @@
|
|||||||
display,
|
display,
|
||||||
macroPreview,
|
macroPreview,
|
||||||
macroValues,
|
macroValues,
|
||||||
selectedCellsPublished(),
|
publishedCells,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -199,7 +201,12 @@
|
|||||||
bind:this={domGrid}
|
bind:this={domGrid}
|
||||||
{...$$props}
|
{...$$props}
|
||||||
setLoadedRows={handleSetLoadedRows}
|
setLoadedRows={handleSetLoadedRows}
|
||||||
bind:selectedCellsPublished
|
onPublishedCellsChanged={value => {
|
||||||
|
publishedCells = value;
|
||||||
|
if (onPublishedCellsChanged) {
|
||||||
|
onPublishedCellsChanged(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
{loadDataPage}
|
{loadDataPage}
|
||||||
{dataPageAvailable}
|
{dataPageAvailable}
|
||||||
{loadRowCount}
|
{loadRowCount}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
export let grider;
|
export let grider;
|
||||||
export let display;
|
export let display;
|
||||||
export let masterLoadedTime = undefined;
|
export let masterLoadedTime = undefined;
|
||||||
export let selectedCellsPublished;
|
|
||||||
export let rowCountLoaded = null;
|
export let rowCountLoaded = null;
|
||||||
|
|
||||||
export let preprocessLoadedRow = null;
|
export let preprocessLoadedRow = null;
|
||||||
@@ -128,7 +127,6 @@
|
|||||||
<DataGridCore
|
<DataGridCore
|
||||||
{...$$props}
|
{...$$props}
|
||||||
bind:this={domGrid}
|
bind:this={domGrid}
|
||||||
bind:selectedCellsPublished
|
|
||||||
onLoadNextData={handleLoadNextData}
|
onLoadNextData={handleLoadNextData}
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
{isLoading}
|
{isLoading}
|
||||||
|
|||||||
@@ -95,7 +95,9 @@
|
|||||||
|
|
||||||
export let macroPreview;
|
export let macroPreview;
|
||||||
export let macroValues;
|
export let macroValues;
|
||||||
export let selectedCellsPublished = () => [];
|
export let onPublishedCellsChanged;
|
||||||
|
|
||||||
|
let publishedCells = [];
|
||||||
|
|
||||||
// export let onChangeGrider = undefined;
|
// export let onChangeGrider = undefined;
|
||||||
|
|
||||||
@@ -116,7 +118,7 @@
|
|||||||
display,
|
display,
|
||||||
macroPreview,
|
macroPreview,
|
||||||
macroValues,
|
macroValues,
|
||||||
selectedCellsPublished()
|
publishedCells
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,7 +226,12 @@
|
|||||||
{dataPageAvailable}
|
{dataPageAvailable}
|
||||||
{loadRowCount}
|
{loadRowCount}
|
||||||
setLoadedRows={handleSetLoadedRows}
|
setLoadedRows={handleSetLoadedRows}
|
||||||
bind:selectedCellsPublished
|
onPublishedCellsChanged={value => {
|
||||||
|
publishedCells = value;
|
||||||
|
if (onPublishedCellsChanged) {
|
||||||
|
onPublishedCellsChanged(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
frameSelection={!!macroPreview}
|
frameSelection={!!macroPreview}
|
||||||
{grider}
|
{grider}
|
||||||
{display}
|
{display}
|
||||||
|
|||||||
@@ -62,18 +62,25 @@
|
|||||||
.main {
|
.main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main.flex1 {
|
.main.flex1 {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: var(--dim-tabs-height);
|
height: var(--dim-tabs-height);
|
||||||
|
min-height: var(--dim-tabs-height);
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: var(--theme-bg-2);
|
background-color: var(--theme-bg-2);
|
||||||
|
overflow-x: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs::-webkit-scrollbar {
|
||||||
|
height: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
export let name;
|
export let name;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let defaultFileName = '';
|
||||||
|
|
||||||
const { values, setFieldValue } = getFormContext();
|
const { values, setFieldValue } = getFormContext();
|
||||||
|
|
||||||
@@ -25,6 +26,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<TextField {...$$restProps} value={$values[name]} on:click={handleBrowse} readOnly {disabled} />
|
<TextField {...$$restProps} value={$values[name] || defaultFileName} on:click={handleBrowse} readOnly {disabled} />
|
||||||
<InlineButton on:click={handleBrowse} {disabled}>Browse</InlineButton>
|
<InlineButton on:click={handleBrowse} {disabled}>Browse</InlineButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
export let options;
|
export let options;
|
||||||
export let isClearable = false;
|
export let isClearable = false;
|
||||||
export let selectFieldComponent = SelectField;
|
export let selectFieldComponent = SelectField;
|
||||||
|
export let defaultSelectValue;
|
||||||
|
|
||||||
const { values, setFieldValue } = getFormContext();
|
const { values, setFieldValue } = getFormContext();
|
||||||
</script>
|
</script>
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
<svelte:component
|
<svelte:component
|
||||||
this={selectFieldComponent}
|
this={selectFieldComponent}
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
value={$values && $values[name]}
|
value={($values && $values[name]) || defaultSelectValue}
|
||||||
options={_.compact(options)}
|
options={_.compact(options)}
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
setFieldValue(name, e.detail);
|
setFieldValue(name, e.detail);
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
'icon sql-generator': 'mdi mdi-cog-transfer',
|
'icon sql-generator': 'mdi mdi-cog-transfer',
|
||||||
'icon keyboard': 'mdi mdi-keyboard-settings',
|
'icon keyboard': 'mdi mdi-keyboard-settings',
|
||||||
'icon settings': 'mdi mdi-cog',
|
'icon settings': 'mdi mdi-cog',
|
||||||
|
'icon users': 'mdi mdi-account-multiple',
|
||||||
|
'icon role': 'mdi mdi-account-group',
|
||||||
|
'icon admin': 'mdi mdi-security',
|
||||||
|
'icon auth': 'mdi mdi-account-key',
|
||||||
'icon version': 'mdi mdi-ticket-confirmation',
|
'icon version': 'mdi mdi-ticket-confirmation',
|
||||||
'icon pin': 'mdi mdi-pin',
|
'icon pin': 'mdi mdi-pin',
|
||||||
'icon arrange': 'mdi mdi-arrange-send-to-back',
|
'icon arrange': 'mdi mdi-arrange-send-to-back',
|
||||||
@@ -185,6 +189,10 @@
|
|||||||
'img app-query': 'mdi mdi-view-comfy color-icon-magenta',
|
'img app-query': 'mdi mdi-view-comfy color-icon-magenta',
|
||||||
'img connection': 'mdi mdi-connection color-icon-blue',
|
'img connection': 'mdi mdi-connection color-icon-blue',
|
||||||
'img profiler': 'mdi mdi-gauge color-icon-blue',
|
'img profiler': 'mdi mdi-gauge color-icon-blue',
|
||||||
|
'img users': 'mdi mdi-account-multiple color-icon-blue',
|
||||||
|
'img role': 'mdi mdi-account-group color-icon-blue',
|
||||||
|
'img admin': 'mdi mdi-security color-icon-blue',
|
||||||
|
'img auth': 'mdi mdi-account-key color-icon-blue',
|
||||||
|
|
||||||
'img add': 'mdi mdi-plus-circle color-icon-green',
|
'img add': 'mdi mdi-plus-circle color-icon-green',
|
||||||
'img minus': 'mdi mdi-minus-circle color-icon-red',
|
'img minus': 'mdi mdi-minus-circle color-icon-red',
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import { useConnectionList } from '../utility/metadataLoaders';
|
import { useConnectionList } from '../utility/metadataLoaders';
|
||||||
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
|
|
||||||
export let allowChooseModel = false;
|
export let allowChooseModel = false;
|
||||||
export let direction;
|
export let direction;
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import localStorageGarbageCollector from './utility/localStorageGarbageCollector
|
|||||||
import { handleOauthCallback } from './clientAuth';
|
import { handleOauthCallback } from './clientAuth';
|
||||||
import LoginPage from './LoginPage.svelte';
|
import LoginPage from './LoginPage.svelte';
|
||||||
import NotLoggedPage from './NotLoggedPage.svelte';
|
import NotLoggedPage from './NotLoggedPage.svelte';
|
||||||
|
import ErrorPage from './ErrorPage.svelte';
|
||||||
const isOauthCallback = handleOauthCallback();
|
|
||||||
|
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const page = params.get('page');
|
const page = params.get('page');
|
||||||
|
|
||||||
|
const isOauthCallback = handleOauthCallback();
|
||||||
|
|
||||||
localStorageGarbageCollector();
|
localStorageGarbageCollector();
|
||||||
|
|
||||||
|
|
||||||
function createApp() {
|
function createApp() {
|
||||||
if (isOauthCallback) {
|
if (isOauthCallback) {
|
||||||
return null;
|
return null;
|
||||||
@@ -22,14 +24,35 @@ function createApp() {
|
|||||||
switch (page) {
|
switch (page) {
|
||||||
case 'login':
|
case 'login':
|
||||||
return new LoginPage({
|
return new LoginPage({
|
||||||
|
target: document.body,
|
||||||
|
props: {
|
||||||
|
isAdminPage: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
case 'error':
|
||||||
|
return new ErrorPage({
|
||||||
target: document.body,
|
target: document.body,
|
||||||
props: {},
|
props: {},
|
||||||
});
|
});
|
||||||
|
case 'admin-login':
|
||||||
|
return new LoginPage({
|
||||||
|
target: document.body,
|
||||||
|
props: {
|
||||||
|
isAdminPage: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
case 'not-logged':
|
case 'not-logged':
|
||||||
return new NotLoggedPage({
|
return new NotLoggedPage({
|
||||||
target: document.body,
|
target: document.body,
|
||||||
props: {},
|
props: {},
|
||||||
});
|
});
|
||||||
|
case 'admin':
|
||||||
|
return new App({
|
||||||
|
target: document.body,
|
||||||
|
props: {
|
||||||
|
isAdminPage: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new App({
|
return new App({
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
import ErrorMessageModal from './ErrorMessageModal.svelte';
|
import ErrorMessageModal from './ErrorMessageModal.svelte';
|
||||||
import ModalBase from './ModalBase.svelte';
|
import ModalBase from './ModalBase.svelte';
|
||||||
import { closeCurrentModal, showModal } from './modalTools';
|
import { closeCurrentModal, showModal } from './modalTools';
|
||||||
|
import { callServerPing } from '../utility/connectionsPinger';
|
||||||
|
|
||||||
export let conid;
|
export let conid;
|
||||||
export let passwordMode;
|
export let passwordMode;
|
||||||
@@ -83,6 +84,7 @@
|
|||||||
isTesting = false;
|
isTesting = false;
|
||||||
if (resp.msgtype == 'connected') {
|
if (resp.msgtype == 'connected') {
|
||||||
setVolatileConnectionRemapping(conid, resp._id);
|
setVolatileConnectionRemapping(conid, resp._id);
|
||||||
|
await callServerPing();
|
||||||
dispatchCacheChange({ key: `server-status-changed` });
|
dispatchCacheChange({ key: `server-status-changed` });
|
||||||
batchDispatchCacheTriggers(x => x.conid == conid);
|
batchDispatchCacheTriggers(x => x.conid == conid);
|
||||||
closeCurrentModal();
|
closeCurrentModal();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
import { closeCurrentModal, showModal } from './modalTools';
|
import { closeCurrentModal, showModal } from './modalTools';
|
||||||
import InputTextModal from './InputTextModal.svelte';
|
import InputTextModal from './InputTextModal.svelte';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
|
|
||||||
export let connection;
|
export let connection;
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
import { currentDropDownMenu } from '../stores';
|
import { currentDropDownMenu } from '../stores';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import { importSqlDump } from '../utility/exportFileTools';
|
import { importSqlDump } from '../utility/exportFileTools';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import getElectron from '../utility/getElectron';
|
import getElectron from '../utility/getElectron';
|
||||||
import { setUploadListener } from '../utility/uploadFiles';
|
import { setUploadListener } from '../utility/uploadFiles';
|
||||||
import ChangeDownloadUrlModal from './ChangeDownloadUrlModal.svelte';
|
import ChangeDownloadUrlModal from './ChangeDownloadUrlModal.svelte';
|
||||||
import ModalBase from './ModalBase.svelte';
|
import ModalBase from './ModalBase.svelte';
|
||||||
import { closeCurrentModal, showModal } from './modalTools';
|
import { closeCurrentModal, showModal } from './modalTools';
|
||||||
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
|
|
||||||
export let connection;
|
export let connection;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
import ModalBase from '../modals/ModalBase.svelte';
|
import ModalBase from '../modals/ModalBase.svelte';
|
||||||
import { closeCurrentModal } from '../modals/modalTools';
|
import { closeCurrentModal } from '../modals/modalTools';
|
||||||
import { fullNameFromString, fullNameToLabel, fullNameToString } from 'dbgate-tools';
|
import { fullNameFromString, fullNameToLabel, fullNameToString, getConnectionLabel } from 'dbgate-tools';
|
||||||
import SelectField from '../forms/SelectField.svelte';
|
import SelectField from '../forms/SelectField.svelte';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import { createPerspectiveNodeConfig, PerspectiveTreeNode } from 'dbgate-datalib';
|
import { createPerspectiveNodeConfig, PerspectiveTreeNode } from 'dbgate-datalib';
|
||||||
import type { ChangePerspectiveConfigFunc, PerspectiveConfig, PerspectiveCustomJoinConfig } from 'dbgate-datalib';
|
import type { ChangePerspectiveConfigFunc, PerspectiveConfig, PerspectiveCustomJoinConfig } from 'dbgate-datalib';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
import TextField from '../forms/TextField.svelte';
|
import TextField from '../forms/TextField.svelte';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { getCurrentDatabase } from '../stores';
|
import { getCurrentDatabase } from '../stores';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
|
||||||
export default function newQuery({
|
export default function newQuery({
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import FormTextField from '../forms/FormTextField.svelte';
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
import { extensions, getCurrentConfig, openedConnections, openedSingleDatabaseConnections } from '../stores';
|
import { extensions, getCurrentConfig, openedConnections, openedSingleDatabaseConnections } from '../stores';
|
||||||
import getElectron from '../utility/getElectron';
|
import getElectron from '../utility/getElectron';
|
||||||
import { useAuthTypes } from '../utility/metadataLoaders';
|
import { useAuthTypes, useConfig } from '../utility/metadataLoaders';
|
||||||
import FormColorField from '../forms/FormColorField.svelte';
|
import FormColorField from '../forms/FormColorField.svelte';
|
||||||
import FontIcon from '../icons/FontIcon.svelte';
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
|
||||||
@@ -27,13 +27,17 @@
|
|||||||
$: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
|
$: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
|
||||||
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||||
$: defaultDatabase = $values.defaultDatabase;
|
$: defaultDatabase = $values.defaultDatabase;
|
||||||
|
$: config = useConfig();
|
||||||
|
|
||||||
$: showUser = driver?.showConnectionField('user', $values) && $values.passwordMode != 'askUser';
|
$: showConnectionFieldArgs = { config: $config };
|
||||||
|
|
||||||
|
$: showUser =
|
||||||
|
driver?.showConnectionField('user', $values, showConnectionFieldArgs) && $values.passwordMode != 'askUser';
|
||||||
$: showPassword =
|
$: showPassword =
|
||||||
driver?.showConnectionField('password', $values) &&
|
driver?.showConnectionField('password', $values, showConnectionFieldArgs) &&
|
||||||
$values.passwordMode != 'askPassword' &&
|
$values.passwordMode != 'askPassword' &&
|
||||||
$values.passwordMode != 'askUser';
|
$values.passwordMode != 'askUser';
|
||||||
$: showPasswordMode = driver?.showConnectionField('password', $values);
|
$: showPasswordMode = driver?.showConnectionField('password', $values, showConnectionFieldArgs);
|
||||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -53,11 +57,11 @@
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if driver?.showConnectionField('databaseFile', $values)}
|
{#if driver?.showConnectionField('databaseFile', $values, showConnectionFieldArgs)}
|
||||||
<FormElectronFileSelector label="Database file" name="databaseFile" disabled={isConnected || !electron} />
|
<FormElectronFileSelector label="Database file" name="databaseFile" disabled={isConnected || !electron} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('useDatabaseUrl', $values)}
|
{#if driver?.showConnectionField('useDatabaseUrl', $values, showConnectionFieldArgs)}
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<FormRadioGroupField
|
<FormRadioGroupField
|
||||||
disabled={isConnected}
|
disabled={isConnected}
|
||||||
@@ -70,7 +74,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('databaseUrl', $values)}
|
{#if driver?.showConnectionField('databaseUrl', $values, showConnectionFieldArgs)}
|
||||||
<FormTextField
|
<FormTextField
|
||||||
label="Database URL"
|
label="Database URL"
|
||||||
name="databaseUrl"
|
name="databaseUrl"
|
||||||
@@ -79,21 +83,27 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $authTypes && driver?.showConnectionField('authType', $values)}
|
{#if $authTypes && driver?.showConnectionField('authType', $values, showConnectionFieldArgs)}
|
||||||
<FormSelectField
|
{#key $authTypes}
|
||||||
label={driver?.authTypeLabel ?? 'Authentication'}
|
<FormSelectField
|
||||||
name="authType"
|
label={driver?.authTypeLabel ?? 'Authentication'}
|
||||||
isNative
|
name="authType"
|
||||||
disabled={isConnected}
|
isNative
|
||||||
defaultValue={driver?.defaultAuthTypeName}
|
disabled={isConnected}
|
||||||
options={$authTypes.map(auth => ({
|
defaultValue={driver?.defaultAuthTypeName}
|
||||||
value: auth.name,
|
options={$authTypes.map(auth => ({
|
||||||
label: auth.title,
|
value: auth.name,
|
||||||
}))}
|
label: auth.title,
|
||||||
/>
|
}))}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('server', $values)}
|
{#if driver?.showConnectionField('clientLibraryPath', $values, showConnectionFieldArgs)}
|
||||||
|
<FormTextField label="Client library path" name="clientLibraryPath" disabled={isConnected} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if driver?.showConnectionField('server', $values, showConnectionFieldArgs)}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-9 mr-1">
|
<div class="col-9 mr-1">
|
||||||
<FormTextField
|
<FormTextField
|
||||||
@@ -103,7 +113,7 @@
|
|||||||
templateProps={{ noMargin: true }}
|
templateProps={{ noMargin: true }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if driver?.showConnectionField('port', $values)}
|
{#if driver?.showConnectionField('port', $values, showConnectionFieldArgs)}
|
||||||
<div class="col-3 mr-1">
|
<div class="col-3 mr-1">
|
||||||
<FormTextField
|
<FormTextField
|
||||||
label="Port"
|
label="Port"
|
||||||
@@ -123,11 +133,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('serviceName', $values)}
|
{#if driver?.showConnectionField('serviceName', $values, showConnectionFieldArgs)}
|
||||||
<FormTextField label="Service name" name="serviceName" disabled={isConnected} />
|
<FormTextField label="Service name" name="serviceName" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('socketPath', $values)}
|
{#if driver?.showConnectionField('socketPath', $values, showConnectionFieldArgs)}
|
||||||
<FormTextField
|
<FormTextField
|
||||||
label="Socket path"
|
label="Socket path"
|
||||||
name="socketPath"
|
name="socketPath"
|
||||||
@@ -183,27 +193,27 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('treeKeySeparator', $values)}
|
{#if driver?.showConnectionField('treeKeySeparator', $values, showConnectionFieldArgs)}
|
||||||
<FormTextField label="Key separator" name="treeKeySeparator" disabled={isConnected} placeholder=":" />
|
<FormTextField label="Key separator" name="treeKeySeparator" disabled={isConnected} placeholder=":" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('windowsDomain', $values)}
|
{#if driver?.showConnectionField('windowsDomain', $values, showConnectionFieldArgs)}
|
||||||
<FormTextField label="Domain (specify to use NTLM authentication)" name="windowsDomain" disabled={isConnected} />
|
<FormTextField label="Domain (specify to use NTLM authentication)" name="windowsDomain" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('isReadOnly', $values)}
|
{#if driver?.showConnectionField('isReadOnly', $values, showConnectionFieldArgs)}
|
||||||
<FormCheckboxField label="Is read only" name="isReadOnly" disabled={isConnected} />
|
<FormCheckboxField label="Is read only" name="isReadOnly" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('trustServerCertificate', $values)}
|
{#if driver?.showConnectionField('trustServerCertificate', $values, showConnectionFieldArgs)}
|
||||||
<FormCheckboxField label="Trust server certificate" name="trustServerCertificate" disabled={isConnected} />
|
<FormCheckboxField label="Trust server certificate" name="trustServerCertificate" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('defaultDatabase', $values)}
|
{#if driver?.showConnectionField('defaultDatabase', $values, showConnectionFieldArgs)}
|
||||||
<FormTextField label="Default database" name="defaultDatabase" disabled={isConnected} />
|
<FormTextField label="Default database" name="defaultDatabase" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if defaultDatabase && driver?.showConnectionField('singleDatabase', $values)}
|
{#if defaultDatabase && driver?.showConnectionField('singleDatabase', $values, showConnectionFieldArgs)}
|
||||||
<FormCheckboxField label={`Use only database ${defaultDatabase}`} name="singleDatabase" disabled={isConnected} />
|
<FormCheckboxField label={`Use only database ${defaultDatabase}`} name="singleDatabase" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
$: useSshTunnel = $values.useSshTunnel;
|
$: useSshTunnel = $values.useSshTunnel;
|
||||||
$: platformInfo = usePlatformInfo();
|
$: platformInfo = usePlatformInfo();
|
||||||
|
|
||||||
$: {
|
// $: {
|
||||||
if (!$values.sshMode) setFieldValue('sshMode', 'userPassword');
|
// if (!$values.sshMode) setFieldValue('sshMode', 'userPassword');
|
||||||
// if (!$values.sshPort) setFieldValue('sshPort', '22');
|
// // if (!$values.sshPort) setFieldValue('sshPort', '22');
|
||||||
if (!$values.sshKeyfile && $platformInfo) setFieldValue('sshKeyfile', $platformInfo.defaultKeyfile);
|
// if (!$values.sshKeyfile && $platformInfo) setFieldValue('sshKeyfile', $platformInfo.defaultKeyfile);
|
||||||
}
|
// }
|
||||||
|
|
||||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||||
</script>
|
</script>
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
label="SSH Authentication"
|
label="SSH Authentication"
|
||||||
name="sshMode"
|
name="sshMode"
|
||||||
isNative
|
isNative
|
||||||
|
defaultSelectValue="userPassword"
|
||||||
disabled={isConnected || !useSshTunnel}
|
disabled={isConnected || !useSshTunnel}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'userPassword', label: 'Username & password' },
|
{ value: 'userPassword', label: 'Username & password' },
|
||||||
@@ -63,11 +64,11 @@
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if $values.sshMode != 'userPassword'}
|
{#if ($values.sshMode || 'userPassword') != 'userPassword'}
|
||||||
<FormTextField label="Login" name="sshLogin" disabled={isConnected || !useSshTunnel} />
|
<FormTextField label="Login" name="sshLogin" disabled={isConnected || !useSshTunnel} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $values.sshMode == 'userPassword'}
|
{#if ($values.sshMode || 'userPassword') == 'userPassword'}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 mr-1">
|
<div class="col-6 mr-1">
|
||||||
<FormTextField
|
<FormTextField
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
name="sshKeyfile"
|
name="sshKeyfile"
|
||||||
disabled={isConnected || !useSshTunnel}
|
disabled={isConnected || !useSshTunnel}
|
||||||
templateProps={{ noMargin: true }}
|
templateProps={{ noMargin: true }}
|
||||||
|
defaultFileName={$platformInfo?.defaultKeyfile}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getSettings, useConfig, useSettings } from './utility/metadataLoaders';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { safeJsonParse } from 'dbgate-tools';
|
import { safeJsonParse } from 'dbgate-tools';
|
||||||
import { apiCall } from './utility/api';
|
import { apiCall } from './utility/api';
|
||||||
|
import { getOpenedTabsStorageName, isAdminPage } from './utility/pageDefs';
|
||||||
|
|
||||||
export interface TabDefinition {
|
export interface TabDefinition {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -72,7 +73,10 @@ function subscribeCssVariable(store, transform, cssVariable) {
|
|||||||
store.subscribe(value => document.documentElement.style.setProperty(cssVariable, transform(value)));
|
store.subscribe(value => document.documentElement.style.setProperty(cssVariable, transform(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const selectedWidget = writableWithStorage('database', 'selectedWidget');
|
export const selectedWidget = writableWithStorage(
|
||||||
|
isAdminPage() ? 'admin' : 'database',
|
||||||
|
isAdminPage() ? 'selectedAdminWidget' : 'selectedWidget'
|
||||||
|
);
|
||||||
export const lockedDatabaseMode = writableWithStorage<boolean>(false, 'lockedDatabaseMode');
|
export const lockedDatabaseMode = writableWithStorage<boolean>(false, 'lockedDatabaseMode');
|
||||||
export const visibleWidgetSideBar = writableWithStorage(true, 'visibleWidgetSideBar');
|
export const visibleWidgetSideBar = writableWithStorage(true, 'visibleWidgetSideBar');
|
||||||
export const visibleSelectedWidget = derived(
|
export const visibleSelectedWidget = derived(
|
||||||
@@ -86,7 +90,7 @@ export const temporaryOpenedConnections = writable([]);
|
|||||||
export const openedSingleDatabaseConnections = writable([]);
|
export const openedSingleDatabaseConnections = writable([]);
|
||||||
export const expandedConnections = writable([]);
|
export const expandedConnections = writable([]);
|
||||||
export const currentDatabase = writable(null);
|
export const currentDatabase = writable(null);
|
||||||
export const openedTabs = writableWithForage<TabDefinition[]>([], 'openedTabs', x => [...(x || [])]);
|
export const openedTabs = writableWithForage<TabDefinition[]>([], getOpenedTabsStorageName(), x => [...(x || [])]);
|
||||||
export const copyRowsFormat = writableWithStorage('textWithoutHeaders', 'copyRowsFormat');
|
export const copyRowsFormat = writableWithStorage('textWithoutHeaders', 'copyRowsFormat');
|
||||||
export const extensions = writable<ExtensionsDirectory>(null);
|
export const extensions = writable<ExtensionsDirectory>(null);
|
||||||
export const visibleCommandPalette = writable(null);
|
export const visibleCommandPalette = writable(null);
|
||||||
|
|||||||
@@ -158,6 +158,7 @@
|
|||||||
|
|
||||||
function getTabDbName(tab, connectionList) {
|
function getTabDbName(tab, connectionList) {
|
||||||
if (tab.tabComponent == 'ConnectionTab') return 'Connections';
|
if (tab.tabComponent == 'ConnectionTab') return 'Connections';
|
||||||
|
if (tab.tabComponent?.startsWith('Admin')) return 'Administration';
|
||||||
if (tab.props && tab.props.conid && tab.props.database) return tab.props.database;
|
if (tab.props && tab.props.conid && tab.props.database) return tab.props.database;
|
||||||
if (tab.props && tab.props.conid) {
|
if (tab.props && tab.props.conid) {
|
||||||
const connection = connectionList?.find(x => x._id == tab.props.conid);
|
const connection = connectionList?.find(x => x._id == tab.props.conid);
|
||||||
@@ -174,6 +175,7 @@
|
|||||||
if (key.startsWith('archive://')) return 'icon archive';
|
if (key.startsWith('archive://')) return 'icon archive';
|
||||||
if (key.startsWith('server://')) return 'icon server';
|
if (key.startsWith('server://')) return 'icon server';
|
||||||
if (key.startsWith('connections.')) return 'icon connection';
|
if (key.startsWith('connections.')) return 'icon connection';
|
||||||
|
if (key.startsWith('admin.')) return 'icon admin';
|
||||||
}
|
}
|
||||||
return 'icon file';
|
return 'icon file';
|
||||||
}
|
}
|
||||||
@@ -285,7 +287,6 @@
|
|||||||
import tabs from '../tabs';
|
import tabs from '../tabs';
|
||||||
import { setSelectedTab } from '../utility/common';
|
import { setSelectedTab } from '../utility/common';
|
||||||
import contextMenu from '../utility/contextMenu';
|
import contextMenu from '../utility/contextMenu';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
|
||||||
import { isElectronAvailable } from '../utility/getElectron';
|
import { isElectronAvailable } from '../utility/getElectron';
|
||||||
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
|
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
|
||||||
import { duplicateTab, getTabDbKey, sortTabs, groupTabs } from '../utility/openNewTab';
|
import { duplicateTab, getTabDbKey, sortTabs, groupTabs } from '../utility/openNewTab';
|
||||||
@@ -293,6 +294,7 @@
|
|||||||
import TabCloseButton from '../elements/TabCloseButton.svelte';
|
import TabCloseButton from '../elements/TabCloseButton.svelte';
|
||||||
import CloseTabModal from '../modals/CloseTabModal.svelte';
|
import CloseTabModal from '../modals/CloseTabModal.svelte';
|
||||||
import SwitchDatabaseModal from '../modals/SwitchDatabaseModal.svelte';
|
import SwitchDatabaseModal from '../modals/SwitchDatabaseModal.svelte';
|
||||||
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
|
|
||||||
export let multiTabIndex;
|
export let multiTabIndex;
|
||||||
export let shownTab;
|
export let shownTab;
|
||||||
|
|||||||
@@ -28,30 +28,37 @@
|
|||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
|
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
|
||||||
import { changeTab } from '../utility/common';
|
import { changeTab } from '../utility/common';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { disconnectServerConnection, openConnection } from '../appobj/ConnectionAppObject.svelte';
|
import { disconnectServerConnection, openConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||||
import { disconnectDatabaseConnection } from '../appobj/DatabaseAppObject.svelte';
|
import { disconnectDatabaseConnection } from '../appobj/DatabaseAppObject.svelte';
|
||||||
|
import { useConfig } from '../utility/metadataLoaders';
|
||||||
|
|
||||||
export let connection;
|
export let connection;
|
||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
|
export let connectionStore = undefined;
|
||||||
|
|
||||||
|
export let onlyTestButton;
|
||||||
|
|
||||||
let isTesting;
|
let isTesting;
|
||||||
let sqlConnectResult;
|
let sqlConnectResult;
|
||||||
|
|
||||||
const values = writable(
|
const values =
|
||||||
connection || {
|
connectionStore ||
|
||||||
server: getCurrentConfig().isDocker ? 'dockerhost' : 'localhost',
|
writable(
|
||||||
engine: '',
|
connection || {
|
||||||
}
|
server: getCurrentConfig().isDocker ? 'dockerhost' : 'localhost',
|
||||||
);
|
engine: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// $: console.log('ConnectionTab.$values', $values);
|
// $: console.log('ConnectionTab.$values', $values);
|
||||||
// $: console.log('ConnectionTab.driver', driver);
|
// $: console.log('ConnectionTab.driver', driver);
|
||||||
|
|
||||||
$: engine = $values.engine;
|
$: engine = $values.engine;
|
||||||
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||||
|
$: config = useConfig();
|
||||||
|
|
||||||
const testIdRef = createRef(0);
|
const testIdRef = createRef(0);
|
||||||
|
|
||||||
@@ -86,7 +93,7 @@
|
|||||||
'socketPath',
|
'socketPath',
|
||||||
'serviceName',
|
'serviceName',
|
||||||
];
|
];
|
||||||
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values));
|
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values, { config: $config }));
|
||||||
const omitProps = _.difference(allProps, visibleProps);
|
const omitProps = _.difference(allProps, visibleProps);
|
||||||
if (!$values.defaultDatabase) omitProps.push('singleDatabase');
|
if (!$values.defaultDatabase) omitProps.push('singleDatabase');
|
||||||
|
|
||||||
@@ -174,6 +181,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function changeConnectionBeforeSave(connection) {
|
||||||
|
if (driver?.beforeConnectionSave) return driver.beforeConnectionSave(connection);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||||
|
|
||||||
// $: console.log('CONN VALUES', $values);
|
// $: console.log('CONN VALUES', $values);
|
||||||
@@ -204,7 +216,13 @@
|
|||||||
{#if driver}
|
{#if driver}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
{#if isConnected}
|
{#if onlyTestButton}
|
||||||
|
{#if isTesting}
|
||||||
|
<FormButton value="Cancel test" on:click={handleCancelTest} />
|
||||||
|
{:else}
|
||||||
|
<FormButton value="Test connection" on:click={handleTest} />
|
||||||
|
{/if}
|
||||||
|
{:else if isConnected}
|
||||||
<FormButton value="Disconnect" on:click={handleDisconnect} />
|
<FormButton value="Disconnect" on:click={handleDisconnect} />
|
||||||
{:else}
|
{:else}
|
||||||
<FormButton value="Connect" on:click={handleConnect} />
|
<FormButton value="Connect" on:click={handleConnect} />
|
||||||
|
|||||||
1
packages/web/src/tabs/index-pro.js
Normal file
1
packages/web/src/tabs/index-pro.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {};
|
||||||
@@ -30,6 +30,8 @@ import * as ProfilerTab from './ProfilerTab.svelte';
|
|||||||
import * as DataDuplicatorTab from './DataDuplicatorTab.svelte';
|
import * as DataDuplicatorTab from './DataDuplicatorTab.svelte';
|
||||||
import * as ImportExportTab from './ImportExportTab.svelte';
|
import * as ImportExportTab from './ImportExportTab.svelte';
|
||||||
|
|
||||||
|
import protabs from './index-pro';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
TableDataTab,
|
TableDataTab,
|
||||||
CollectionDataTab,
|
CollectionDataTab,
|
||||||
@@ -62,4 +64,5 @@ export default {
|
|||||||
ProfilerTab,
|
ProfilerTab,
|
||||||
DataDuplicatorTab,
|
DataDuplicatorTab,
|
||||||
ImportExportTab,
|
ImportExportTab,
|
||||||
|
...protabs,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { TabDefinition } from '../stores';
|
import { TabDefinition } from '../stores';
|
||||||
import getElectron from './getElectron';
|
import getElectron from './getElectron';
|
||||||
|
import { getOpenedTabsStorageName } from './pageDefs';
|
||||||
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
$: counterCopy = counter;
|
$: counterCopy = counter;
|
||||||
@@ -26,15 +27,15 @@
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
let openedTabs = (await localforage.getItem<TabDefinition[]>('openedTabs')) || [];
|
let openedTabs = (await localforage.getItem<TabDefinition[]>(getOpenedTabsStorageName())) || [];
|
||||||
if (!_.isArray(openedTabs)) openedTabs = [];
|
if (!_.isArray(openedTabs)) openedTabs = [];
|
||||||
openedTabs = openedTabs
|
openedTabs = openedTabs
|
||||||
.map(tab => (tab.closedTime ? tab : { ...tab, closedTime: new Date().getTime() }))
|
.map(tab => (tab.closedTime ? tab : { ...tab, closedTime: new Date().getTime() }))
|
||||||
.map(tab => ({ ...tab, selected: false }));
|
.map(tab => ({ ...tab, selected: false }));
|
||||||
await localforage.setItem('openedTabs', openedTabs);
|
await localforage.setItem(getOpenedTabsStorageName(), openedTabs);
|
||||||
await localStorage.setItem('selectedWidget', 'history');
|
await localStorage.setItem('selectedWidget', 'history');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
localforage.removeItem('openedTabs');
|
localforage.removeItem(getOpenedTabsStorageName());
|
||||||
}
|
}
|
||||||
// try {
|
// try {
|
||||||
// await localforage.clear();
|
// await localforage.clear();
|
||||||
|
|||||||
8
packages/web/src/utility/InnerActivator.svelte
Normal file
8
packages/web/src/utility/InnerActivator.svelte
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import createActivator from './createActivator';
|
||||||
|
|
||||||
|
export let name;
|
||||||
|
export let activateOnTabVisible = false;
|
||||||
|
|
||||||
|
export const activator = createActivator(name, activateOnTabVisible);
|
||||||
|
</script>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
import runCommand from '../commands/runCommand';
|
import runCommand from '../commands/runCommand';
|
||||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||||
import { showModal } from '../modals/modalTools';
|
import { showModal } from '../modals/modalTools';
|
||||||
import { openedTabs } from '../stores';
|
import { commandsCustomized, openedTabs } from '../stores';
|
||||||
|
|
||||||
import { getConfig, getConnectionList, useFavorites } from './metadataLoaders';
|
import { getConfig, getConnectionList, useFavorites } from './metadataLoaders';
|
||||||
import openNewTab from './openNewTab';
|
import openNewTab from './openNewTab';
|
||||||
@@ -49,7 +49,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$openedTabs.find(x => x.closedTime == null) && !(await getConnectionList()).find(x => !x.unsaved)) {
|
if (
|
||||||
|
!$openedTabs.find(x => x.closedTime == null) &&
|
||||||
|
!(await getConnectionList()).find(x => !x.unsaved) &&
|
||||||
|
$commandsCustomized['new.connection']?.enabled
|
||||||
|
) {
|
||||||
openNewTab({
|
openNewTab({
|
||||||
title: 'New Connection',
|
title: 'New Connection',
|
||||||
icon: 'img connection',
|
icon: 'img connection',
|
||||||
|
|||||||
@@ -4,22 +4,37 @@ import { writable } from 'svelte/store';
|
|||||||
import getElectron from './getElectron';
|
import getElectron from './getElectron';
|
||||||
// import socket from './socket';
|
// import socket from './socket';
|
||||||
import { showSnackbarError } from '../utility/snackbar';
|
import { showSnackbarError } from '../utility/snackbar';
|
||||||
import { isOauthCallback, redirectToLogin } from '../clientAuth';
|
import { isOauthCallback, redirectToAdminLogin, redirectToLogin } from '../clientAuth';
|
||||||
import { showModal } from '../modals/modalTools';
|
import { showModal } from '../modals/modalTools';
|
||||||
import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte';
|
import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
|
import { openWebLink } from './exportFileTools';
|
||||||
|
import { callServerPing } from './connectionsPinger';
|
||||||
|
import { batchDispatchCacheTriggers, dispatchCacheChange } from './cache';
|
||||||
|
|
||||||
export const strmid = uuidv1();
|
export const strmid = uuidv1();
|
||||||
|
|
||||||
let eventSource;
|
let eventSource;
|
||||||
let apiLogging = false;
|
let apiLogging = true;
|
||||||
// let cacheCleanerRegistered;
|
// let cacheCleanerRegistered;
|
||||||
let apiDisabled = false;
|
let apiDisabled = false;
|
||||||
const disabledOnOauth = isOauthCallback();
|
const disabledOnOauth = isOauthCallback();
|
||||||
|
|
||||||
const volatileConnectionMap = {};
|
export const volatileConnectionMapStore = writable({});
|
||||||
const volatileConnectionMapInv = {};
|
export const volatileConnectionMapInvStore = writable({});
|
||||||
|
|
||||||
|
let volatileConnectionMapValue = {};
|
||||||
|
volatileConnectionMapStore.subscribe(value => {
|
||||||
|
volatileConnectionMapValue = value;
|
||||||
|
});
|
||||||
|
export const getVolatileConnectionMap = () => volatileConnectionMapValue;
|
||||||
|
|
||||||
|
let volatileConnectionMapInvValue = {};
|
||||||
|
volatileConnectionMapInvStore.subscribe(value => {
|
||||||
|
volatileConnectionMapInvValue = value;
|
||||||
|
});
|
||||||
|
export const getVolatileConnectionInvMap = () => volatileConnectionMapInvValue;
|
||||||
|
|
||||||
export function disableApi() {
|
export function disableApi() {
|
||||||
apiDisabled = true;
|
apiDisabled = true;
|
||||||
@@ -30,23 +45,29 @@ export function enableApi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setVolatileConnectionRemapping(existingConnectionId, volatileConnectionId) {
|
export function setVolatileConnectionRemapping(existingConnectionId, volatileConnectionId) {
|
||||||
volatileConnectionMap[existingConnectionId] = volatileConnectionId;
|
volatileConnectionMapStore.update(x => ({
|
||||||
volatileConnectionMapInv[volatileConnectionId] = existingConnectionId;
|
...x,
|
||||||
|
[existingConnectionId]: volatileConnectionId,
|
||||||
|
}));
|
||||||
|
volatileConnectionMapInvStore.update(x => ({
|
||||||
|
...x,
|
||||||
|
[volatileConnectionId]: existingConnectionId,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVolatileRemapping(conid) {
|
export function getVolatileRemapping(conid) {
|
||||||
return volatileConnectionMap[conid] || conid;
|
return volatileConnectionMapValue[conid] || conid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVolatileRemappingInv(conid) {
|
export function getVolatileRemappingInv(conid) {
|
||||||
return volatileConnectionMapInv[conid] || conid;
|
return volatileConnectionMapInvValue[conid] || conid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeVolatileMapping(conid) {
|
export function removeVolatileMapping(conid) {
|
||||||
const mapped = volatileConnectionMap[conid];
|
const mapped = volatileConnectionMapValue[conid];
|
||||||
if (mapped) {
|
if (mapped) {
|
||||||
delete volatileConnectionMap[conid];
|
volatileConnectionMapStore.update(x => _.omit(x, conid));
|
||||||
delete volatileConnectionMapInv[mapped];
|
volatileConnectionMapInvStore.update(x => _.omit(x, mapped));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +84,15 @@ function processApiResponse(route, args, resp) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
if (resp?.missingCredentials) {
|
if (resp?.missingCredentials) {
|
||||||
if (!isDatabaseLoginVisible()) {
|
if (resp.detail.redirectToDbLogin) {
|
||||||
|
const state = `dbg-dblogin:${strmid}:${resp.detail.conid}`;
|
||||||
|
localStorage.setItem('dbloginState', state);
|
||||||
|
openWebLink(
|
||||||
|
`connections/dblogin?conid=${resp.detail.conid}&state=${encodeURIComponent(state)}&redirectUri=${
|
||||||
|
location.origin + location.pathname
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
} else if (!isDatabaseLoginVisible()) {
|
||||||
showModal(DatabaseLoginModal, resp.detail);
|
showModal(DatabaseLoginModal, resp.detail);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -83,16 +112,16 @@ function processApiResponse(route, args, resp) {
|
|||||||
|
|
||||||
export function transformApiArgs(args) {
|
export function transformApiArgs(args) {
|
||||||
return _.mapValues(args, (v, k) => {
|
return _.mapValues(args, (v, k) => {
|
||||||
if (k == 'conid' && v && volatileConnectionMap[v]) return volatileConnectionMap[v];
|
if (k == 'conid' && v && volatileConnectionMapValue[v]) return volatileConnectionMapValue[v];
|
||||||
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMap[x] || x);
|
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMapValue[x] || x);
|
||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformApiArgsInv(args) {
|
export function transformApiArgsInv(args) {
|
||||||
return _.mapValues(args, (v, k) => {
|
return _.mapValues(args, (v, k) => {
|
||||||
if (k == 'conid' && v && volatileConnectionMapInv[v]) return volatileConnectionMapInv[v];
|
if (k == 'conid' && v && volatileConnectionMapInvValue[v]) return volatileConnectionMapInvValue[v];
|
||||||
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMapInv[x] || x);
|
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMapInvValue[x] || x);
|
||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -132,9 +161,13 @@ export async function apiCall(route: string, args: {} = undefined) {
|
|||||||
|
|
||||||
disableApi();
|
disableApi();
|
||||||
console.log('Disabling API', route);
|
console.log('Disabling API', route);
|
||||||
if (params.get('page') != 'login' && params.get('page') != 'not-logged') {
|
if (params.get('page') != 'login' && params.get('page') != 'admin-login' && params.get('page') != 'not-logged') {
|
||||||
// unauthorized
|
// unauthorized
|
||||||
redirectToLogin();
|
if (params.get('page') == 'admin') {
|
||||||
|
redirectToAdminLogin();
|
||||||
|
} else {
|
||||||
|
redirectToLogin();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -205,6 +238,19 @@ export function useApiCall(route, args, defaultValue) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getVolatileConnections() {
|
||||||
|
return Object.values(volatileConnectionMapValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function installNewVolatileConnectionListener() {
|
||||||
|
apiOn('got-volatile-token', async ({ savedConId, volatileConId }) => {
|
||||||
|
setVolatileConnectionRemapping(savedConId, volatileConId);
|
||||||
|
await callServerPing();
|
||||||
|
dispatchCacheChange({ key: `server-status-changed` });
|
||||||
|
batchDispatchCacheTriggers(x => x.conid == savedConId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function enableApiLog() {
|
function enableApiLog() {
|
||||||
apiLogging = true;
|
apiLogging = true;
|
||||||
console.log('API loggin enabled');
|
console.log('API loggin enabled');
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { getOpenedTabs, openedTabs } from '../stores';
|
import { getOpenedTabs, openedTabs } from '../stores';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import getElectron from './getElectron';
|
|
||||||
|
|
||||||
export class LoadingToken {
|
export class LoadingToken {
|
||||||
isCanceled = false;
|
isCanceled = false;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { openedConnections, currentDatabase, openedConnectionsWithTemporary } from '../stores';
|
import { currentDatabase, openedConnectionsWithTemporary, getCurrentConfig, getOpenedConnections } from '../stores';
|
||||||
import { apiCall, strmid } from './api';
|
import { apiCall, getVolatileConnections, strmid } from './api';
|
||||||
import { getConnectionList } from './metadataLoaders';
|
import hasPermission from '../utility/hasPermission';
|
||||||
|
|
||||||
// const doServerPing = async value => {
|
// const doServerPing = async value => {
|
||||||
// const connectionList = getConnectionList();
|
// const connectionList = getConnectionList();
|
||||||
@@ -10,7 +10,21 @@ import { getConnectionList } from './metadataLoaders';
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
const doServerPing = value => {
|
const doServerPing = value => {
|
||||||
apiCall('server-connections/ping', { conidArray: value, strmid });
|
const config = getCurrentConfig();
|
||||||
|
|
||||||
|
const conidArray = [...value];
|
||||||
|
if (config.storageDatabase && hasPermission('internal-storage')) {
|
||||||
|
conidArray.push('__storage');
|
||||||
|
}
|
||||||
|
conidArray.push(...getVolatileConnections());
|
||||||
|
if (config.singleConnection) {
|
||||||
|
conidArray.push(config.singleConnection._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
apiCall('server-connections/ping', {
|
||||||
|
conidArray,
|
||||||
|
strmid,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const doDatabasePing = value => {
|
const doDatabasePing = value => {
|
||||||
@@ -38,3 +52,8 @@ export function subscribeConnectionPingers() {
|
|||||||
currentDatabaseHandle = window.setInterval(() => doDatabasePing(value), 20 * 1000);
|
currentDatabaseHandle = window.setInterval(() => doDatabasePing(value), 20 * 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function callServerPing() {
|
||||||
|
const connections = getOpenedConnections();
|
||||||
|
doServerPing(connections);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import { useConfig } from './metadataLoaders';
|
|||||||
let compiled = null;
|
let compiled = null;
|
||||||
|
|
||||||
export default function hasPermission(tested) {
|
export default function hasPermission(tested) {
|
||||||
|
// console.log('TESTING PERM', tested, compiled, testPermission(tested, compiled));
|
||||||
return testPermission(tested, compiled);
|
return testPermission(tested, compiled);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribePermissionCompiler() {
|
export function subscribePermissionCompiler() {
|
||||||
|
// console.log('subscribePermissionCompiler', compiled);
|
||||||
|
|
||||||
useConfig().subscribe(value => {
|
useConfig().subscribe(value => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
const { permissions } = value;
|
const { permissions } = value;
|
||||||
compiled = compilePermissions(permissions);
|
compiled = compilePermissions(permissions);
|
||||||
|
// console.log('COMPILED PERMS', compiled);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import localforage from 'localforage';
|
import localforage from 'localforage';
|
||||||
|
import { getOpenedTabsStorageName } from './pageDefs';
|
||||||
|
|
||||||
export default async function localStorageGarbageCollector() {
|
export default async function localStorageGarbageCollector() {
|
||||||
const openedTabsJson = await localforage.getItem('openedTabs');
|
const openedTabsJson = await localforage.getItem(getOpenedTabsStorageName());
|
||||||
let openedTabs = openedTabsJson ?? [];
|
let openedTabs = openedTabsJson ?? [];
|
||||||
|
|
||||||
const closeLimit = moment().add(-7, 'day').valueOf();
|
const closeLimit = moment().add(-7, 'day').valueOf();
|
||||||
|
|
||||||
openedTabs = openedTabs.filter(x => !x.closedTime || x.closedTime > closeLimit);
|
openedTabs = openedTabs.filter(x => !x.closedTime || x.closedTime > closeLimit);
|
||||||
await localforage.setItem('openedTabs', openedTabs);
|
await localforage.setItem(getOpenedTabsStorageName(), openedTabs);
|
||||||
|
|
||||||
const toRemove = [];
|
const toRemove = [];
|
||||||
for (const key in localStorage) {
|
for (const key in localStorage) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ImportExportModal from '../modals/ImportExportModal.svelte';
|
|||||||
import getElectron from './getElectron';
|
import getElectron from './getElectron';
|
||||||
import { currentDatabase, extensions, getCurrentDatabase } from '../stores';
|
import { currentDatabase, extensions, getCurrentDatabase } from '../stores';
|
||||||
import { getUploadListener } from './uploadFiles';
|
import { getUploadListener } from './uploadFiles';
|
||||||
import getConnectionLabel, { getDatabaseFileLabel } from './getConnectionLabel';
|
import {getConnectionLabel, getDatabaseFileLabel } from 'dbgate-tools';
|
||||||
import { apiCall } from './api';
|
import { apiCall } from './api';
|
||||||
import openNewTab from './openNewTab';
|
import openNewTab from './openNewTab';
|
||||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||||
|
|||||||
@@ -152,6 +152,9 @@ export function getTabDbKey(tab) {
|
|||||||
if (tab.tabComponent == 'ConnectionTab') {
|
if (tab.tabComponent == 'ConnectionTab') {
|
||||||
return 'connections.';
|
return 'connections.';
|
||||||
}
|
}
|
||||||
|
if (tab.tabComponent?.startsWith('Admin')) {
|
||||||
|
return 'admin.';
|
||||||
|
}
|
||||||
if (tab.props && tab.props.conid && tab.props.database) {
|
if (tab.props && tab.props.conid && tab.props.database) {
|
||||||
return `database://${tab.props.database}-${tab.props.conid}`;
|
return `database://${tab.props.database}-${tab.props.conid}`;
|
||||||
}
|
}
|
||||||
|
|||||||
15
packages/web/src/utility/pageDefs.ts
Normal file
15
packages/web/src/utility/pageDefs.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
let isAdminPageCache;
|
||||||
|
|
||||||
|
export function isAdminPage() {
|
||||||
|
if (isAdminPageCache == null) {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const urlPage = params.get('page');
|
||||||
|
|
||||||
|
isAdminPageCache = urlPage == 'admin';
|
||||||
|
}
|
||||||
|
return isAdminPageCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOpenedTabsStorageName() {
|
||||||
|
return isAdminPage() ? 'adminOpenedTabs' : 'openedTabs';
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import getElectron from './getElectron';
|
import getElectron from './getElectron';
|
||||||
|
import { isAdminPage } from './pageDefs';
|
||||||
|
|
||||||
let apiUrl = null;
|
let apiUrl = null;
|
||||||
try {
|
try {
|
||||||
@@ -16,9 +17,12 @@ export function resolveApiHeaders() {
|
|||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
|
|
||||||
const res = {};
|
const res = {};
|
||||||
const accessToken = localStorage.getItem('accessToken');
|
const accessToken = localStorage.getItem(isAdminPage() ? 'adminAccessToken' : 'accessToken');
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
res['Authorization'] = `Bearer ${accessToken}`;
|
res['Authorization'] = `Bearer ${accessToken}`;
|
||||||
}
|
}
|
||||||
|
if (isAdminPage()) {
|
||||||
|
res['x-is-admin-page'] = 'true';
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
1
packages/web/src/widgets/AdminMenuWidget.svelte
Normal file
1
packages/web/src/widgets/AdminMenuWidget.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Sorry, administration is not available
|
||||||
@@ -18,11 +18,11 @@
|
|||||||
collapsedConnectionGroupNames,
|
collapsedConnectionGroupNames,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import runCommand from '../commands/runCommand';
|
import runCommand from '../commands/runCommand';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
||||||
import FontIcon from '../icons/FontIcon.svelte';
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||||
import { apiCall, getVolatileRemapping } from '../utility/api';
|
import { apiCall, volatileConnectionMapStore } from '../utility/api';
|
||||||
import LargeButton from '../buttons/LargeButton.svelte';
|
import LargeButton from '../buttons/LargeButton.svelte';
|
||||||
import { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons';
|
import { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons';
|
||||||
import { safeJsonParse } from 'dbgate-tools';
|
import { safeJsonParse } from 'dbgate-tools';
|
||||||
@@ -37,7 +37,10 @@
|
|||||||
|
|
||||||
$: connectionsWithStatus =
|
$: connectionsWithStatus =
|
||||||
$connections && $serverStatus
|
$connections && $serverStatus
|
||||||
? $connections.map(conn => ({ ...conn, status: $serverStatus[getVolatileRemapping(conn._id)] }))
|
? $connections.map(conn => ({
|
||||||
|
...conn,
|
||||||
|
status: $serverStatus[$volatileConnectionMapStore[conn._id] || conn._id],
|
||||||
|
}))
|
||||||
: $connections;
|
: $connections;
|
||||||
|
|
||||||
$: connectionsWithStatusFiltered = connectionsWithStatus?.filter(
|
$: connectionsWithStatusFiltered = connectionsWithStatus?.filter(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
selectedWidget,
|
selectedWidget,
|
||||||
visibleCommandPalette,
|
visibleCommandPalette,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
import { useConnectionList, useDatabaseServerVersion, useDatabaseStatus } from '../utility/metadataLoaders';
|
import { useConnectionList, useDatabaseServerVersion, useDatabaseStatus } from '../utility/metadataLoaders';
|
||||||
import { findCommand } from '../commands/runCommand';
|
import { findCommand } from '../commands/runCommand';
|
||||||
import { useConnectionColor } from '../utility/useConnectionColor';
|
import { useConnectionColor } from '../utility/useConnectionColor';
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import CellDataWidget from './CellDataWidget.svelte';
|
import CellDataWidget from './CellDataWidget.svelte';
|
||||||
import HistoryWidget from './HistoryWidget.svelte';
|
import HistoryWidget from './HistoryWidget.svelte';
|
||||||
import AppWidget from './AppWidget.svelte';
|
import AppWidget from './AppWidget.svelte';
|
||||||
|
import AdminMenuWidget from './AdminMenuWidget.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DatabaseWidget hidden={$visibleSelectedWidget != 'database'} />
|
<DatabaseWidget hidden={$visibleSelectedWidget != 'database'} />
|
||||||
@@ -29,3 +30,6 @@
|
|||||||
{#if $visibleSelectedWidget == 'app'}
|
{#if $visibleSelectedWidget == 'app'}
|
||||||
<AppWidget />
|
<AppWidget />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $visibleSelectedWidget == 'admin'}
|
||||||
|
<AdminMenuWidget />
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
visibleWidgetSideBar,
|
visibleWidgetSideBar,
|
||||||
visibleHamburgerMenuWidget,
|
visibleHamburgerMenuWidget,
|
||||||
lockedDatabaseMode,
|
lockedDatabaseMode,
|
||||||
|
getCurrentConfig,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
|
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
|
||||||
import hasPermission from '../utility/hasPermission';
|
import hasPermission from '../utility/hasPermission';
|
||||||
@@ -16,6 +17,11 @@
|
|||||||
let domMainMenu;
|
let domMainMenu;
|
||||||
|
|
||||||
const widgets = [
|
const widgets = [
|
||||||
|
getCurrentConfig().storageDatabase && {
|
||||||
|
icon: 'icon admin',
|
||||||
|
name: 'admin',
|
||||||
|
title: 'Administration',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: 'icon database',
|
icon: 'icon database',
|
||||||
name: 'database',
|
name: 'database',
|
||||||
@@ -98,7 +104,7 @@
|
|||||||
<FontIcon icon="icon menu" />
|
<FontIcon icon="icon menu" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each widgets.filter(x => hasPermission(`widgets/${x.name}`)) as item}
|
{#each widgets.filter(x => x && hasPermission(`widgets/${x.name}`)) as item}
|
||||||
<div
|
<div
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:selected={item.name == $visibleSelectedWidget}
|
class:selected={item.name == $visibleSelectedWidget}
|
||||||
@@ -119,6 +125,7 @@
|
|||||||
>
|
>
|
||||||
<FontIcon icon={$lockedDatabaseMode ? 'icon locked-database-mode' : 'icon unlocked-database-mode'} />
|
<FontIcon icon={$lockedDatabaseMode ? 'icon locked-database-mode' : 'icon unlocked-database-mode'} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wrapper" on:click={handleSettingsMenu} bind:this={domSettings}>
|
<div class="wrapper" on:click={handleSettingsMenu} bind:this={domSettings}>
|
||||||
<FontIcon icon="icon settings" />
|
<FontIcon icon="icon settings" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
collections: [
|
collections: [
|
||||||
...collections.map((x, index) => ({
|
...collections.map((x, index) => ({
|
||||||
pureName: x.name,
|
pureName: x.name,
|
||||||
tableRowCount: stats[index].count,
|
tableRowCount: stats[index]?.count,
|
||||||
})),
|
})),
|
||||||
...views.map((x, index) => ({
|
...views.map((x, index) => ({
|
||||||
pureName: x.name,
|
pureName: x.name,
|
||||||
|
|||||||
@@ -31,12 +31,13 @@
|
|||||||
"plugout": "dbgate-plugout dbgate-plugin-mssql"
|
"plugout": "dbgate-plugout dbgate-plugin-mssql"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"async-lock": "^1.2.6",
|
||||||
|
"@azure/msal-node": "^2.12.0",
|
||||||
"dbgate-plugin-tools": "^1.0.7",
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
"dbgate-query-splitter": "^4.10.1",
|
"dbgate-query-splitter": "^4.10.1",
|
||||||
"webpack": "^5.91.0",
|
|
||||||
"webpack-cli": "^5.1.4",
|
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
"tedious": "^18.2.0",
|
"tedious": "^18.2.0",
|
||||||
"async-lock": "^1.2.6"
|
"webpack": "^5.91.0",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
plugins/dbgate-plugin-mssql/src/backend/azureAuth.js
Normal file
22
plugins/dbgate-plugin-mssql/src/backend/azureAuth.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
function getAzureAuthTypes(platformInfo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function azureGetRedirectAuthUrl(connection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function azureGetAuthTokenFromCode(connection, code) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAzureAuthOptions(connection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAzureAuthTypes,
|
||||||
|
azureGetRedirectAuthUrl,
|
||||||
|
azureGetAuthTokenFromCode,
|
||||||
|
getAzureAuthOptions,
|
||||||
|
};
|
||||||
@@ -8,8 +8,11 @@ const AsyncLock = require('async-lock');
|
|||||||
const nativeDriver = require('./nativeDriver');
|
const nativeDriver = require('./nativeDriver');
|
||||||
const lock = new AsyncLock();
|
const lock = new AsyncLock();
|
||||||
const { tediousConnect, tediousQueryCore, tediousReadQuery, tediousStream } = require('./tediousDriver');
|
const { tediousConnect, tediousQueryCore, tediousReadQuery, tediousStream } = require('./tediousDriver');
|
||||||
|
const { getAzureAuthTypes, azureGetRedirectAuthUrl, azureGetAuthTokenFromCode } = require('./azureAuth');
|
||||||
const { nativeConnect, nativeQueryCore, nativeReadQuery, nativeStream } = nativeDriver;
|
const { nativeConnect, nativeQueryCore, nativeReadQuery, nativeStream } = nativeDriver;
|
||||||
|
|
||||||
let requireMsnodesqlv8;
|
let requireMsnodesqlv8;
|
||||||
|
let platformInfo;
|
||||||
|
|
||||||
const versionQuery = `
|
const versionQuery = `
|
||||||
SELECT
|
SELECT
|
||||||
@@ -52,7 +55,14 @@ const driver = {
|
|||||||
analyserClass: MsSqlAnalyser,
|
analyserClass: MsSqlAnalyser,
|
||||||
|
|
||||||
getAuthTypes() {
|
getAuthTypes() {
|
||||||
return requireMsnodesqlv8 ? windowsAuthTypes : null;
|
const res = [];
|
||||||
|
if (requireMsnodesqlv8) res.push(...windowsAuthTypes);
|
||||||
|
const azureAuthTypes = getAzureAuthTypes(platformInfo);
|
||||||
|
if (azureAuthTypes) res.push(...azureAuthTypes);
|
||||||
|
if (res.length > 0) {
|
||||||
|
return _.uniqBy(res, 'name');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async connect(conn) {
|
async connect(conn) {
|
||||||
@@ -115,12 +125,19 @@ const driver = {
|
|||||||
const { rows } = await this.query(pool, 'SELECT name FROM sys.databases order by name');
|
const { rows } = await this.query(pool, 'SELECT name FROM sys.databases order by name');
|
||||||
return rows;
|
return rows;
|
||||||
},
|
},
|
||||||
|
getRedirectAuthUrl(connection, options) {
|
||||||
|
return azureGetRedirectAuthUrl(connection, options);
|
||||||
|
},
|
||||||
|
getAuthTokenFromCode(connection, options) {
|
||||||
|
return azureGetAuthTokenFromCode(connection, options);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
driver.initialize = dbgateEnv => {
|
driver.initialize = dbgateEnv => {
|
||||||
if (dbgateEnv.nativeModules && dbgateEnv.nativeModules.msnodesqlv8) {
|
if (dbgateEnv.nativeModules && dbgateEnv.nativeModules.msnodesqlv8) {
|
||||||
requireMsnodesqlv8 = dbgateEnv.nativeModules.msnodesqlv8;
|
requireMsnodesqlv8 = dbgateEnv.nativeModules.msnodesqlv8;
|
||||||
}
|
}
|
||||||
|
platformInfo = dbgateEnv.platformInfo;
|
||||||
nativeDriver.initialize(dbgateEnv);
|
nativeDriver.initialize(dbgateEnv);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user