mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-01 01:33:59 +00:00
SYNC: Merge pull request #9 from dbgate/feature/apps
This commit is contained in:
@@ -10,6 +10,7 @@ module.exports = ({ editMenu, isMac }) => [
|
|||||||
{ command: 'new.queryDesign', hideDisabled: true },
|
{ command: 'new.queryDesign', hideDisabled: true },
|
||||||
{ command: 'new.diagram', hideDisabled: true },
|
{ command: 'new.diagram', hideDisabled: true },
|
||||||
{ command: 'new.perspective', hideDisabled: true },
|
{ command: 'new.perspective', hideDisabled: true },
|
||||||
|
{ command: 'new.application', hideDisabled: true },
|
||||||
{ command: 'new.shell', hideDisabled: true },
|
{ command: 'new.shell', hideDisabled: true },
|
||||||
{ command: 'new.jsonl', hideDisabled: true },
|
{ command: 'new.jsonl', hideDisabled: true },
|
||||||
{ command: 'new.modelTransform', hideDisabled: true },
|
{ command: 'new.modelTransform', hideDisabled: true },
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ class AuthProviderBase {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCurrentFilePermissions(req) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
getLoginPageConnections() {
|
getLoginPageConnections() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,233 +1,98 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { appdir } = require('../utility/directories');
|
const { appdir, filesdir } = require('../utility/directories');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
const connections = require('./connections');
|
const connections = require('./connections');
|
||||||
|
const {
|
||||||
|
loadPermissionsFromRequest,
|
||||||
|
loadFilePermissionsFromRequest,
|
||||||
|
hasPermission,
|
||||||
|
getFilePermissionRole,
|
||||||
|
} = require('../utility/hasPermission');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
folders_meta: true,
|
getAllApps_meta: true,
|
||||||
async folders() {
|
async getAllApps({}, req) {
|
||||||
const folders = await fs.readdir(appdir());
|
const dir = path.join(filesdir(), 'apps');
|
||||||
return [
|
|
||||||
...folders.map(name => ({
|
|
||||||
name,
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
createFolder_meta: true,
|
|
||||||
async createFolder({ folder }) {
|
|
||||||
const name = await this.getNewAppFolder({ name: folder });
|
|
||||||
await fs.mkdir(path.join(appdir(), name));
|
|
||||||
socket.emitChanged('app-folders-changed');
|
|
||||||
this.emitChangedDbApp(folder);
|
|
||||||
return name;
|
|
||||||
},
|
|
||||||
|
|
||||||
files_meta: true,
|
|
||||||
async files({ folder }) {
|
|
||||||
if (!folder) return [];
|
|
||||||
const dir = path.join(appdir(), folder);
|
|
||||||
if (!(await fs.exists(dir))) return [];
|
|
||||||
const files = await fs.readdir(dir);
|
|
||||||
|
|
||||||
function fileType(ext, type) {
|
|
||||||
return files
|
|
||||||
.filter(name => name.endsWith(ext))
|
|
||||||
.map(name => ({
|
|
||||||
name: name.slice(0, -ext.length),
|
|
||||||
label: path.parse(name.slice(0, -ext.length)).base,
|
|
||||||
type,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
...fileType('.command.sql', 'command.sql'),
|
|
||||||
...fileType('.query.sql', 'query.sql'),
|
|
||||||
...fileType('.config.json', 'config.json'),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
async emitChangedDbApp(folder) {
|
|
||||||
const used = await this.getUsedAppFolders();
|
|
||||||
if (used.includes(folder)) {
|
|
||||||
socket.emitChanged('used-apps-changed');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
refreshFiles_meta: true,
|
|
||||||
async refreshFiles({ folder }) {
|
|
||||||
socket.emitChanged('app-files-changed', { app: folder });
|
|
||||||
},
|
|
||||||
|
|
||||||
refreshFolders_meta: true,
|
|
||||||
async refreshFolders() {
|
|
||||||
socket.emitChanged(`app-folders-changed`);
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteFile_meta: true,
|
|
||||||
async deleteFile({ folder, file, fileType }) {
|
|
||||||
await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`));
|
|
||||||
socket.emitChanged('app-files-changed', { app: folder });
|
|
||||||
this.emitChangedDbApp(folder);
|
|
||||||
},
|
|
||||||
|
|
||||||
renameFile_meta: true,
|
|
||||||
async renameFile({ folder, file, newFile, fileType }) {
|
|
||||||
await fs.rename(
|
|
||||||
path.join(path.join(appdir(), folder), `${file}.${fileType}`),
|
|
||||||
path.join(path.join(appdir(), folder), `${newFile}.${fileType}`)
|
|
||||||
);
|
|
||||||
socket.emitChanged('app-files-changed', { app: folder });
|
|
||||||
this.emitChangedDbApp(folder);
|
|
||||||
},
|
|
||||||
|
|
||||||
renameFolder_meta: true,
|
|
||||||
async renameFolder({ folder, newFolder }) {
|
|
||||||
const uniqueName = await this.getNewAppFolder({ name: newFolder });
|
|
||||||
await fs.rename(path.join(appdir(), folder), path.join(appdir(), uniqueName));
|
|
||||||
socket.emitChanged(`app-folders-changed`);
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteFolder_meta: true,
|
|
||||||
async deleteFolder({ folder }) {
|
|
||||||
if (!folder) throw new Error('Missing folder parameter');
|
|
||||||
await fs.rmdir(path.join(appdir(), folder), { recursive: true });
|
|
||||||
socket.emitChanged(`app-folders-changed`);
|
|
||||||
socket.emitChanged('app-files-changed', { app: folder });
|
|
||||||
socket.emitChanged('used-apps-changed');
|
|
||||||
},
|
|
||||||
|
|
||||||
async getNewAppFolder({ name }) {
|
|
||||||
if (!(await fs.exists(path.join(appdir(), name)))) return name;
|
|
||||||
let index = 2;
|
|
||||||
while (await fs.exists(path.join(appdir(), `${name}${index}`))) {
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
return `${name}${index}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
getUsedAppFolders_meta: true,
|
|
||||||
async getUsedAppFolders() {
|
|
||||||
const list = await connections.list();
|
|
||||||
const apps = [];
|
|
||||||
|
|
||||||
for (const connection of list) {
|
|
||||||
for (const db of connection.databases || []) {
|
|
||||||
for (const key of _.keys(db || {})) {
|
|
||||||
if (key.startsWith('useApp:') && db[key]) {
|
|
||||||
apps.push(key.substring('useApp:'.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.intersection(_.uniq(apps), await fs.readdir(appdir()));
|
|
||||||
},
|
|
||||||
|
|
||||||
getUsedApps_meta: true,
|
|
||||||
async getUsedApps() {
|
|
||||||
const apps = await this.getUsedAppFolders();
|
|
||||||
const res = [];
|
const res = [];
|
||||||
|
const loadedPermissions = await loadPermissionsFromRequest(req);
|
||||||
|
const filePermissions = await loadFilePermissionsFromRequest(req);
|
||||||
|
|
||||||
for (const folder of apps) {
|
for (const file of await fs.readdir(dir)) {
|
||||||
res.push(await this.loadApp({ folder }));
|
if (!hasPermission(`all-files`, loadedPermissions)) {
|
||||||
}
|
const role = getFilePermissionRole('apps', file, filePermissions);
|
||||||
return res;
|
if (role == 'deny') continue;
|
||||||
},
|
|
||||||
|
|
||||||
// getAppsForDb_meta: true,
|
|
||||||
// async getAppsForDb({ conid, database }) {
|
|
||||||
// const connection = await connections.get({ conid });
|
|
||||||
// if (!connection) return [];
|
|
||||||
// const db = (connection.databases || []).find(x => x.name == database);
|
|
||||||
// const apps = [];
|
|
||||||
// const res = [];
|
|
||||||
// if (db) {
|
|
||||||
// for (const key of _.keys(db || {})) {
|
|
||||||
// if (key.startsWith('useApp:') && db[key]) {
|
|
||||||
// apps.push(key.substring('useApp:'.length));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// for (const folder of apps) {
|
|
||||||
// res.push(await this.loadApp({ folder }));
|
|
||||||
// }
|
|
||||||
// return res;
|
|
||||||
// },
|
|
||||||
|
|
||||||
loadApp_meta: true,
|
|
||||||
async loadApp({ folder }) {
|
|
||||||
const res = {
|
|
||||||
queries: [],
|
|
||||||
commands: [],
|
|
||||||
name: folder,
|
|
||||||
};
|
|
||||||
const dir = path.join(appdir(), folder);
|
|
||||||
if (await fs.exists(dir)) {
|
|
||||||
const files = await fs.readdir(dir);
|
|
||||||
|
|
||||||
async function processType(ext, field) {
|
|
||||||
for (const file of files) {
|
|
||||||
if (file.endsWith(ext)) {
|
|
||||||
res[field].push({
|
|
||||||
name: file.slice(0, -ext.length),
|
|
||||||
sql: await fs.readFile(path.join(dir, file), { encoding: 'utf-8' }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const content = await fs.readFile(path.join(dir, file), { encoding: 'utf-8' });
|
||||||
|
const appJson = JSON.parse(content);
|
||||||
|
// const app = {
|
||||||
|
// appid: file,
|
||||||
|
// name: appJson.applicationName,
|
||||||
|
// usageRules: appJson.usageRules || [],
|
||||||
|
// icon: appJson.applicationIcon || 'img app',
|
||||||
|
// color: appJson.applicationColor,
|
||||||
|
// queries: Object.values(appJson.files || {})
|
||||||
|
// .filter(x => x.type == 'query')
|
||||||
|
// .map(x => ({
|
||||||
|
// name: x.label,
|
||||||
|
// sql: x.sql,
|
||||||
|
// })),
|
||||||
|
// commands: Object.values(appJson.files || {})
|
||||||
|
// .filter(x => x.type == 'command')
|
||||||
|
// .map(x => ({
|
||||||
|
// name: x.label,
|
||||||
|
// sql: x.sql,
|
||||||
|
// })),
|
||||||
|
// virtualReferences: appJson.virtualReferences,
|
||||||
|
// dictionaryDescriptions: appJson.dictionaryDescriptions,
|
||||||
|
// };
|
||||||
|
const app = {
|
||||||
|
...appJson,
|
||||||
|
appid: file,
|
||||||
|
};
|
||||||
|
|
||||||
await processType('.command.sql', 'commands');
|
res.push(app);
|
||||||
await processType('.query.sql', 'queries');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
res.virtualReferences = JSON.parse(
|
|
||||||
await fs.readFile(path.join(dir, 'virtual-references.config.json'), { encoding: 'utf-8' })
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
res.virtualReferences = [];
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
res.dictionaryDescriptions = JSON.parse(
|
|
||||||
await fs.readFile(path.join(dir, 'dictionary-descriptions.config.json'), { encoding: 'utf-8' })
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
res.dictionaryDescriptions = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveConfigFile(appFolder, filename, filterFunc, newItem) {
|
createAppFromDb_meta: true,
|
||||||
const file = path.join(appdir(), appFolder, filename);
|
async createAppFromDb({ appName, server, database }, req) {
|
||||||
|
const appdir = path.join(filesdir(), 'apps');
|
||||||
let json;
|
if (!fs.existsSync(appdir)) {
|
||||||
try {
|
await fs.mkdir(appdir);
|
||||||
json = JSON.parse(await fs.readFile(file, { encoding: 'utf-8' }));
|
|
||||||
} catch (err) {
|
|
||||||
json = [];
|
|
||||||
}
|
}
|
||||||
|
const appId = _.kebabCase(appName);
|
||||||
if (filterFunc) {
|
let suffix = undefined;
|
||||||
json = json.filter(filterFunc);
|
while (fs.existsSync(path.join(appdir, `${appId}${suffix || ''}`))) {
|
||||||
|
if (!suffix) suffix = 2;
|
||||||
|
else suffix++;
|
||||||
}
|
}
|
||||||
|
const finalAppId = `${appId}${suffix || ''}`;
|
||||||
|
|
||||||
json = [...json, newItem];
|
const appJson = {
|
||||||
|
applicationName: appName,
|
||||||
|
usageRules: [
|
||||||
|
{
|
||||||
|
serverHostsList: server,
|
||||||
|
databaseNamesList: database,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
await fs.writeFile(file, JSON.stringify(json, undefined, 2));
|
await fs.writeFile(path.join(appdir, `${finalAppId}`), JSON.stringify(appJson, undefined, 2));
|
||||||
|
|
||||||
socket.emitChanged('app-files-changed', { app: appFolder });
|
socket.emitChanged(`files-changed`, { folder: 'apps' });
|
||||||
socket.emitChanged('used-apps-changed');
|
|
||||||
|
return finalAppId;
|
||||||
},
|
},
|
||||||
|
|
||||||
saveVirtualReference_meta: true,
|
saveVirtualReference_meta: true,
|
||||||
async saveVirtualReference({ appFolder, schemaName, pureName, refSchemaName, refTableName, columns }) {
|
async saveVirtualReference({ appid, schemaName, pureName, refSchemaName, refTableName, columns }) {
|
||||||
await this.saveConfigFile(
|
await this.saveConfigItem(
|
||||||
appFolder,
|
appid,
|
||||||
'virtual-references.config.json',
|
'virtualReferences',
|
||||||
columns.length == 1
|
columns.length == 1
|
||||||
? x =>
|
? x =>
|
||||||
!(
|
!(
|
||||||
@@ -245,14 +110,17 @@ module.exports = {
|
|||||||
columns,
|
columns,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
socket.emitChanged(`files-changed`, { folder: 'apps' });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
saveDictionaryDescription_meta: true,
|
saveDictionaryDescription_meta: true,
|
||||||
async saveDictionaryDescription({ appFolder, pureName, schemaName, expression, columns, delimiter }) {
|
async saveDictionaryDescription({ appid, pureName, schemaName, expression, columns, delimiter }) {
|
||||||
await this.saveConfigFile(
|
await this.saveConfigItem(
|
||||||
appFolder,
|
appid,
|
||||||
'dictionary-descriptions.config.json',
|
'dictionaryDescriptions',
|
||||||
x => !(x.schemaName == schemaName && x.pureName == pureName),
|
x => !(x.schemaName == schemaName && x.pureName == pureName),
|
||||||
{
|
{
|
||||||
schemaName,
|
schemaName,
|
||||||
@@ -263,18 +131,271 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
socket.emitChanged(`files-changed`, { folder: 'apps' });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
createConfigFile_meta: true,
|
async saveConfigItem(appid, fieldName, filterFunc, newItem) {
|
||||||
async createConfigFile({ appFolder, fileName, content }) {
|
const file = path.join(filesdir(), 'apps', appid);
|
||||||
const file = path.join(appdir(), appFolder, fileName);
|
|
||||||
if (!(await fs.exists(file))) {
|
const appJson = JSON.parse(await fs.readFile(file, { encoding: 'utf-8' }));
|
||||||
await fs.writeFile(file, JSON.stringify(content, undefined, 2));
|
let json = appJson[fieldName] || [];
|
||||||
socket.emitChanged('app-files-changed', { app: appFolder });
|
|
||||||
socket.emitChanged('used-apps-changed');
|
if (filterFunc) {
|
||||||
return true;
|
json = json.filter(filterFunc);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
json = [...json, newItem];
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
file,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
...appJson,
|
||||||
|
[fieldName]: json,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.emitChanged('files-changed', { folder: 'apps' });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// folders_meta: true,
|
||||||
|
// async folders() {
|
||||||
|
// const folders = await fs.readdir(appdir());
|
||||||
|
// return [
|
||||||
|
// ...folders.map(name => ({
|
||||||
|
// name,
|
||||||
|
// })),
|
||||||
|
// ];
|
||||||
|
// },
|
||||||
|
|
||||||
|
// createFolder_meta: true,
|
||||||
|
// async createFolder({ folder }) {
|
||||||
|
// const name = await this.getNewAppFolder({ name: folder });
|
||||||
|
// await fs.mkdir(path.join(appdir(), name));
|
||||||
|
// socket.emitChanged('app-folders-changed');
|
||||||
|
// this.emitChangedDbApp(folder);
|
||||||
|
// return name;
|
||||||
|
// },
|
||||||
|
|
||||||
|
// files_meta: true,
|
||||||
|
// async files({ folder }) {
|
||||||
|
// if (!folder) return [];
|
||||||
|
// const dir = path.join(appdir(), folder);
|
||||||
|
// if (!(await fs.exists(dir))) return [];
|
||||||
|
// const files = await fs.readdir(dir);
|
||||||
|
|
||||||
|
// function fileType(ext, type) {
|
||||||
|
// return files
|
||||||
|
// .filter(name => name.endsWith(ext))
|
||||||
|
// .map(name => ({
|
||||||
|
// name: name.slice(0, -ext.length),
|
||||||
|
// label: path.parse(name.slice(0, -ext.length)).base,
|
||||||
|
// type,
|
||||||
|
// }));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return [
|
||||||
|
// ...fileType('.command.sql', 'command.sql'),
|
||||||
|
// ...fileType('.query.sql', 'query.sql'),
|
||||||
|
// ...fileType('.config.json', 'config.json'),
|
||||||
|
// ];
|
||||||
|
// },
|
||||||
|
|
||||||
|
// async emitChangedDbApp(folder) {
|
||||||
|
// const used = await this.getUsedAppFolders();
|
||||||
|
// if (used.includes(folder)) {
|
||||||
|
// socket.emitChanged('used-apps-changed');
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
// refreshFiles_meta: true,
|
||||||
|
// async refreshFiles({ folder }) {
|
||||||
|
// socket.emitChanged('app-files-changed', { app: folder });
|
||||||
|
// },
|
||||||
|
|
||||||
|
// refreshFolders_meta: true,
|
||||||
|
// async refreshFolders() {
|
||||||
|
// socket.emitChanged(`app-folders-changed`);
|
||||||
|
// },
|
||||||
|
|
||||||
|
// deleteFile_meta: true,
|
||||||
|
// async deleteFile({ folder, file, fileType }) {
|
||||||
|
// await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`));
|
||||||
|
// socket.emitChanged('app-files-changed', { app: folder });
|
||||||
|
// this.emitChangedDbApp(folder);
|
||||||
|
// },
|
||||||
|
|
||||||
|
// renameFile_meta: true,
|
||||||
|
// async renameFile({ folder, file, newFile, fileType }) {
|
||||||
|
// await fs.rename(
|
||||||
|
// path.join(path.join(appdir(), folder), `${file}.${fileType}`),
|
||||||
|
// path.join(path.join(appdir(), folder), `${newFile}.${fileType}`)
|
||||||
|
// );
|
||||||
|
// socket.emitChanged('app-files-changed', { app: folder });
|
||||||
|
// this.emitChangedDbApp(folder);
|
||||||
|
// },
|
||||||
|
|
||||||
|
// renameFolder_meta: true,
|
||||||
|
// async renameFolder({ folder, newFolder }) {
|
||||||
|
// const uniqueName = await this.getNewAppFolder({ name: newFolder });
|
||||||
|
// await fs.rename(path.join(appdir(), folder), path.join(appdir(), uniqueName));
|
||||||
|
// socket.emitChanged(`app-folders-changed`);
|
||||||
|
// },
|
||||||
|
|
||||||
|
// deleteFolder_meta: true,
|
||||||
|
// async deleteFolder({ folder }) {
|
||||||
|
// if (!folder) throw new Error('Missing folder parameter');
|
||||||
|
// await fs.rmdir(path.join(appdir(), folder), { recursive: true });
|
||||||
|
// socket.emitChanged(`app-folders-changed`);
|
||||||
|
// socket.emitChanged('app-files-changed', { app: folder });
|
||||||
|
// socket.emitChanged('used-apps-changed');
|
||||||
|
// },
|
||||||
|
|
||||||
|
// async getNewAppFolder({ name }) {
|
||||||
|
// if (!(await fs.exists(path.join(appdir(), name)))) return name;
|
||||||
|
// let index = 2;
|
||||||
|
// while (await fs.exists(path.join(appdir(), `${name}${index}`))) {
|
||||||
|
// index += 1;
|
||||||
|
// }
|
||||||
|
// return `${name}${index}`;
|
||||||
|
// },
|
||||||
|
|
||||||
|
// getUsedAppFolders_meta: true,
|
||||||
|
// async getUsedAppFolders() {
|
||||||
|
// const list = await connections.list();
|
||||||
|
// const apps = [];
|
||||||
|
|
||||||
|
// for (const connection of list) {
|
||||||
|
// for (const db of connection.databases || []) {
|
||||||
|
// for (const key of _.keys(db || {})) {
|
||||||
|
// if (key.startsWith('useApp:') && db[key]) {
|
||||||
|
// apps.push(key.substring('useApp:'.length));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return _.intersection(_.uniq(apps), await fs.readdir(appdir()));
|
||||||
|
// },
|
||||||
|
|
||||||
|
// // getAppsForDb_meta: true,
|
||||||
|
// // async getAppsForDb({ conid, database }) {
|
||||||
|
// // const connection = await connections.get({ conid });
|
||||||
|
// // if (!connection) return [];
|
||||||
|
// // const db = (connection.databases || []).find(x => x.name == database);
|
||||||
|
// // const apps = [];
|
||||||
|
// // const res = [];
|
||||||
|
// // if (db) {
|
||||||
|
// // for (const key of _.keys(db || {})) {
|
||||||
|
// // if (key.startsWith('useApp:') && db[key]) {
|
||||||
|
// // apps.push(key.substring('useApp:'.length));
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // for (const folder of apps) {
|
||||||
|
// // res.push(await this.loadApp({ folder }));
|
||||||
|
// // }
|
||||||
|
// // return res;
|
||||||
|
// // },
|
||||||
|
|
||||||
|
// loadApp_meta: true,
|
||||||
|
// async loadApp({ folder }) {
|
||||||
|
// const res = {
|
||||||
|
// queries: [],
|
||||||
|
// commands: [],
|
||||||
|
// name: folder,
|
||||||
|
// };
|
||||||
|
// const dir = path.join(appdir(), folder);
|
||||||
|
// if (await fs.exists(dir)) {
|
||||||
|
// const files = await fs.readdir(dir);
|
||||||
|
|
||||||
|
// async function processType(ext, field) {
|
||||||
|
// for (const file of files) {
|
||||||
|
// if (file.endsWith(ext)) {
|
||||||
|
// res[field].push({
|
||||||
|
// name: file.slice(0, -ext.length),
|
||||||
|
// sql: await fs.readFile(path.join(dir, file), { encoding: 'utf-8' }),
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await processType('.command.sql', 'commands');
|
||||||
|
// await processType('.query.sql', 'queries');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// res.virtualReferences = JSON.parse(
|
||||||
|
// await fs.readFile(path.join(dir, 'virtual-references.config.json'), { encoding: 'utf-8' })
|
||||||
|
// );
|
||||||
|
// } catch (err) {
|
||||||
|
// res.virtualReferences = [];
|
||||||
|
// }
|
||||||
|
// try {
|
||||||
|
// res.dictionaryDescriptions = JSON.parse(
|
||||||
|
// await fs.readFile(path.join(dir, 'dictionary-descriptions.config.json'), { encoding: 'utf-8' })
|
||||||
|
// );
|
||||||
|
// } catch (err) {
|
||||||
|
// res.dictionaryDescriptions = [];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return res;
|
||||||
|
// },
|
||||||
|
|
||||||
|
// async saveConfigFile(appFolder, filename, filterFunc, newItem) {
|
||||||
|
// const file = path.join(appdir(), appFolder, filename);
|
||||||
|
|
||||||
|
// let json;
|
||||||
|
// try {
|
||||||
|
// json = JSON.parse(await fs.readFile(file, { encoding: 'utf-8' }));
|
||||||
|
// } catch (err) {
|
||||||
|
// json = [];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (filterFunc) {
|
||||||
|
// json = json.filter(filterFunc);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// json = [...json, newItem];
|
||||||
|
|
||||||
|
// await fs.writeFile(file, JSON.stringify(json, undefined, 2));
|
||||||
|
|
||||||
|
// socket.emitChanged('app-files-changed', { app: appFolder });
|
||||||
|
// socket.emitChanged('used-apps-changed');
|
||||||
|
// },
|
||||||
|
|
||||||
|
// saveDictionaryDescription_meta: true,
|
||||||
|
// async saveDictionaryDescription({ appFolder, pureName, schemaName, expression, columns, delimiter }) {
|
||||||
|
// await this.saveConfigFile(
|
||||||
|
// appFolder,
|
||||||
|
// 'dictionary-descriptions.config.json',
|
||||||
|
// x => !(x.schemaName == schemaName && x.pureName == pureName),
|
||||||
|
// {
|
||||||
|
// schemaName,
|
||||||
|
// pureName,
|
||||||
|
// expression,
|
||||||
|
// columns,
|
||||||
|
// delimiter,
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return true;
|
||||||
|
// },
|
||||||
|
|
||||||
|
// createConfigFile_meta: true,
|
||||||
|
// async createConfigFile({ appFolder, fileName, content }) {
|
||||||
|
// const file = path.join(appdir(), appFolder, fileName);
|
||||||
|
// if (!(await fs.exists(file))) {
|
||||||
|
// await fs.writeFile(file, JSON.stringify(content, undefined, 2));
|
||||||
|
// socket.emitChanged('app-files-changed', { app: appFolder });
|
||||||
|
// socket.emitChanged('used-apps-changed');
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ const path = require('path');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const { filesdir, archivedir, resolveArchiveFolder, uploadsdir, appdir, jsldir } = require('../utility/directories');
|
const { filesdir, archivedir, resolveArchiveFolder, uploadsdir, appdir, jsldir } = require('../utility/directories');
|
||||||
const getChartExport = require('../utility/getChartExport');
|
const getChartExport = require('../utility/getChartExport');
|
||||||
const { hasPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
|
const {
|
||||||
|
hasPermission,
|
||||||
|
loadPermissionsFromRequest,
|
||||||
|
loadFilePermissionsFromRequest,
|
||||||
|
getFilePermissionRole,
|
||||||
|
} = require('../utility/hasPermission');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
const scheduler = require('./scheduler');
|
const scheduler = require('./scheduler');
|
||||||
const getDiagramExport = require('../utility/getDiagramExport');
|
const getDiagramExport = require('../utility/getDiagramExport');
|
||||||
|
|||||||
@@ -745,6 +745,88 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pureName": "file_permission_roles",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"pureName": "file_permission_roles",
|
||||||
|
"columnName": "id",
|
||||||
|
"dataType": "int",
|
||||||
|
"autoIncrement": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "file_permission_roles",
|
||||||
|
"columnName": "name",
|
||||||
|
"dataType": "varchar(100)",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [],
|
||||||
|
"primaryKey": {
|
||||||
|
"pureName": "file_permission_roles",
|
||||||
|
"constraintType": "primaryKey",
|
||||||
|
"constraintName": "PK_file_permission_roles",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"preloadedRows": [
|
||||||
|
{
|
||||||
|
"id": -1,
|
||||||
|
"name": "allow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": -2,
|
||||||
|
"name": "deny"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "roles",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"pureName": "roles",
|
||||||
|
"columnName": "id",
|
||||||
|
"dataType": "int",
|
||||||
|
"autoIncrement": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "roles",
|
||||||
|
"columnName": "name",
|
||||||
|
"dataType": "varchar(250)",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [],
|
||||||
|
"primaryKey": {
|
||||||
|
"pureName": "roles",
|
||||||
|
"constraintType": "primaryKey",
|
||||||
|
"constraintName": "PK_roles",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"preloadedRows": [
|
||||||
|
{
|
||||||
|
"id": -1,
|
||||||
|
"name": "anonymous-user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": -2,
|
||||||
|
"name": "logged-user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": -3,
|
||||||
|
"name": "superadmin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pureName": "role_connections",
|
"pureName": "role_connections",
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -899,6 +981,85 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pureName": "role_files",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"pureName": "role_files",
|
||||||
|
"columnName": "id",
|
||||||
|
"dataType": "int",
|
||||||
|
"autoIncrement": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "role_files",
|
||||||
|
"columnName": "role_id",
|
||||||
|
"dataType": "int",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "role_files",
|
||||||
|
"columnName": "folder_name",
|
||||||
|
"dataType": "varchar(100)",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "role_files",
|
||||||
|
"columnName": "file_names_list",
|
||||||
|
"dataType": "varchar(1000)",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "role_files",
|
||||||
|
"columnName": "file_names_regex",
|
||||||
|
"dataType": "varchar(1000)",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "role_files",
|
||||||
|
"columnName": "file_permission_role_id",
|
||||||
|
"dataType": "int",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"constraintType": "foreignKey",
|
||||||
|
"constraintName": "FK_role_files_role_id",
|
||||||
|
"pureName": "role_files",
|
||||||
|
"refTableName": "roles",
|
||||||
|
"deleteAction": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "role_id",
|
||||||
|
"refColumnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constraintType": "foreignKey",
|
||||||
|
"constraintName": "FK_role_files_file_permission_role_id",
|
||||||
|
"pureName": "role_files",
|
||||||
|
"refTableName": "file_permission_roles",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "file_permission_role_id",
|
||||||
|
"refColumnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"pureName": "role_files",
|
||||||
|
"constraintType": "primaryKey",
|
||||||
|
"constraintName": "PK_role_files",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pureName": "role_permissions",
|
"pureName": "role_permissions",
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -1082,49 +1243,6 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"pureName": "roles",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"pureName": "roles",
|
|
||||||
"columnName": "id",
|
|
||||||
"dataType": "int",
|
|
||||||
"autoIncrement": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pureName": "roles",
|
|
||||||
"columnName": "name",
|
|
||||||
"dataType": "varchar(250)",
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"foreignKeys": [],
|
|
||||||
"primaryKey": {
|
|
||||||
"pureName": "roles",
|
|
||||||
"constraintType": "primaryKey",
|
|
||||||
"constraintName": "PK_roles",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"columnName": "id"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"preloadedRows": [
|
|
||||||
{
|
|
||||||
"id": -1,
|
|
||||||
"name": "anonymous-user"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": -2,
|
|
||||||
"name": "logged-user"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": -3,
|
|
||||||
"name": "superadmin"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"pureName": "table_permission_roles",
|
"pureName": "table_permission_roles",
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -1243,6 +1361,47 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pureName": "users",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"pureName": "users",
|
||||||
|
"columnName": "id",
|
||||||
|
"dataType": "int",
|
||||||
|
"autoIncrement": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "users",
|
||||||
|
"columnName": "login",
|
||||||
|
"dataType": "varchar(250)",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "users",
|
||||||
|
"columnName": "password",
|
||||||
|
"dataType": "varchar(250)",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "users",
|
||||||
|
"columnName": "email",
|
||||||
|
"dataType": "varchar(250)",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [],
|
||||||
|
"primaryKey": {
|
||||||
|
"pureName": "users",
|
||||||
|
"constraintType": "primaryKey",
|
||||||
|
"constraintName": "PK_users",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pureName": "user_connections",
|
"pureName": "user_connections",
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -1397,6 +1556,85 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pureName": "user_files",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"pureName": "user_files",
|
||||||
|
"columnName": "id",
|
||||||
|
"dataType": "int",
|
||||||
|
"autoIncrement": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "user_files",
|
||||||
|
"columnName": "user_id",
|
||||||
|
"dataType": "int",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "user_files",
|
||||||
|
"columnName": "folder_name",
|
||||||
|
"dataType": "varchar(100)",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "user_files",
|
||||||
|
"columnName": "file_names_list",
|
||||||
|
"dataType": "varchar(1000)",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "user_files",
|
||||||
|
"columnName": "file_names_regex",
|
||||||
|
"dataType": "varchar(1000)",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "user_files",
|
||||||
|
"columnName": "file_permission_role_id",
|
||||||
|
"dataType": "int",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"constraintType": "foreignKey",
|
||||||
|
"constraintName": "FK_user_files_user_id",
|
||||||
|
"pureName": "user_files",
|
||||||
|
"refTableName": "users",
|
||||||
|
"deleteAction": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "user_id",
|
||||||
|
"refColumnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constraintType": "foreignKey",
|
||||||
|
"constraintName": "FK_user_files_file_permission_role_id",
|
||||||
|
"pureName": "user_files",
|
||||||
|
"refTableName": "file_permission_roles",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "file_permission_role_id",
|
||||||
|
"refColumnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"pureName": "user_files",
|
||||||
|
"constraintType": "primaryKey",
|
||||||
|
"constraintName": "PK_user_files",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"columnName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pureName": "user_permissions",
|
"pureName": "user_permissions",
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -1641,47 +1879,6 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"pureName": "users",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"pureName": "users",
|
|
||||||
"columnName": "id",
|
|
||||||
"dataType": "int",
|
|
||||||
"autoIncrement": true,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pureName": "users",
|
|
||||||
"columnName": "login",
|
|
||||||
"dataType": "varchar(250)",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pureName": "users",
|
|
||||||
"columnName": "password",
|
|
||||||
"dataType": "varchar(250)",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pureName": "users",
|
|
||||||
"columnName": "email",
|
|
||||||
"dataType": "varchar(250)",
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"foreignKeys": [],
|
|
||||||
"primaryKey": {
|
|
||||||
"pureName": "users",
|
|
||||||
"constraintType": "primaryKey",
|
|
||||||
"constraintName": "PK_users",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"columnName": "id"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"collections": [],
|
"collections": [],
|
||||||
|
|||||||
@@ -85,6 +85,16 @@ async function loadTablePermissionsFromRequest(req) {
|
|||||||
return tablePermissions;
|
return tablePermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadFilePermissionsFromRequest(req) {
|
||||||
|
const authProvider = getAuthProviderFromReq(req);
|
||||||
|
if (!req) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePermissions = await authProvider.getCurrentFilePermissions(req);
|
||||||
|
return filePermissions;
|
||||||
|
}
|
||||||
|
|
||||||
function matchDatabasePermissionRow(conid, database, permissionRow) {
|
function matchDatabasePermissionRow(conid, database, permissionRow) {
|
||||||
if (permissionRow.connection_id) {
|
if (permissionRow.connection_id) {
|
||||||
if (conid != permissionRow.connection_id) {
|
if (conid != permissionRow.connection_id) {
|
||||||
@@ -135,6 +145,27 @@ function matchTablePermissionRow(objectTypeField, schemaName, pureName, permissi
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function matchFilePermissionRow(folder, file, permissionRow) {
|
||||||
|
if (permissionRow.folder_name) {
|
||||||
|
if (folder != permissionRow.folder_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (permissionRow.file_names_list) {
|
||||||
|
const items = permissionRow.file_names_list.split('\n');
|
||||||
|
if (!items.find(item => item.trim()?.toLowerCase() === file?.toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (permissionRow.file_names_regex) {
|
||||||
|
const regex = new RegExp(permissionRow.file_names_regex, 'i');
|
||||||
|
if (!regex.test(file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const DATABASE_ROLE_ID_NAMES = {
|
const DATABASE_ROLE_ID_NAMES = {
|
||||||
'-1': 'view',
|
'-1': 'view',
|
||||||
'-2': 'read_content',
|
'-2': 'read_content',
|
||||||
@@ -143,6 +174,11 @@ const DATABASE_ROLE_ID_NAMES = {
|
|||||||
'-5': 'deny',
|
'-5': 'deny',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FILE_ROLE_ID_NAMES = {
|
||||||
|
'-1': 'allow',
|
||||||
|
'-2': 'deny',
|
||||||
|
};
|
||||||
|
|
||||||
function getDatabaseRoleLevelIndex(roleName) {
|
function getDatabaseRoleLevelIndex(roleName) {
|
||||||
if (!roleName) {
|
if (!roleName) {
|
||||||
return 6;
|
return 6;
|
||||||
@@ -198,6 +234,17 @@ function getDatabasePermissionRole(conid, database, loadedDatabasePermissions) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFilePermissionRole(folder, file, loadedFilePermissions) {
|
||||||
|
let res = 'deny';
|
||||||
|
for (const permissionRow of loadedFilePermissions) {
|
||||||
|
if (!matchFilePermissionRow(folder, file, permissionRow)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
res = FILE_ROLE_ID_NAMES[permissionRow.file_permission_role_id];
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
const TABLE_ROLE_ID_NAMES = {
|
const TABLE_ROLE_ID_NAMES = {
|
||||||
'-1': 'read',
|
'-1': 'read',
|
||||||
'-2': 'update_only',
|
'-2': 'update_only',
|
||||||
@@ -308,8 +355,10 @@ module.exports = {
|
|||||||
loadPermissionsFromRequest,
|
loadPermissionsFromRequest,
|
||||||
loadDatabasePermissionsFromRequest,
|
loadDatabasePermissionsFromRequest,
|
||||||
loadTablePermissionsFromRequest,
|
loadTablePermissionsFromRequest,
|
||||||
|
loadFilePermissionsFromRequest,
|
||||||
getDatabasePermissionRole,
|
getDatabasePermissionRole,
|
||||||
getTablePermissionRole,
|
getTablePermissionRole,
|
||||||
|
getFilePermissionRole,
|
||||||
testStandardPermission,
|
testStandardPermission,
|
||||||
testDatabaseRolePermission,
|
testDatabaseRolePermission,
|
||||||
getTablePermissionRoleLevelIndex,
|
getTablePermissionRoleLevelIndex,
|
||||||
|
|||||||
@@ -111,3 +111,20 @@ export function fillConstraintNames(table: TableInfo, dialect: SqlDialect) {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DATA_FOLDER_NAMES = [
|
||||||
|
{ name: 'sql', label: 'SQL scripts' },
|
||||||
|
{ name: 'shell', label: 'Shell scripts' },
|
||||||
|
{ name: 'markdown', label: 'Markdown files' },
|
||||||
|
{ name: 'charts', label: 'Charts' },
|
||||||
|
{ name: 'query', label: 'Query designs' },
|
||||||
|
{ name: 'sqlite', label: 'SQLite files' },
|
||||||
|
{ name: 'duckdb', label: 'DuckDB files' },
|
||||||
|
{ name: 'diagrams', label: 'Diagrams' },
|
||||||
|
{ name: 'perspectives', label: 'Perspectives' },
|
||||||
|
{ name: 'impexp', label: 'Import/Export jobs' },
|
||||||
|
{ name: 'modtrans', label: 'Model transforms' },
|
||||||
|
{ name: 'datadeploy', label: 'Data deploy jobs' },
|
||||||
|
{ name: 'dbcompare', label: 'Database compare jobs' },
|
||||||
|
{ name: 'apps', label: 'Applications' },
|
||||||
|
];
|
||||||
|
|||||||
50
packages/types/appdefs.d.ts
vendored
50
packages/types/appdefs.d.ts
vendored
@@ -1,12 +1,12 @@
|
|||||||
interface ApplicationCommand {
|
// interface ApplicationCommand {
|
||||||
name: string;
|
// name: string;
|
||||||
sql: string;
|
// sql: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
interface ApplicationQuery {
|
// interface ApplicationQuery {
|
||||||
name: string;
|
// name: string;
|
||||||
sql: string;
|
// sql: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
interface VirtualReferenceDefinition {
|
interface VirtualReferenceDefinition {
|
||||||
pureName: string;
|
pureName: string;
|
||||||
@@ -27,11 +27,31 @@ interface DictionaryDescriptionDefinition {
|
|||||||
delimiter: string;
|
delimiter: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplicationDefinition {
|
interface ApplicationUsageRule {
|
||||||
name: string;
|
conditionGroup?: string;
|
||||||
|
serverHostsRegex?: string;
|
||||||
queries: ApplicationQuery[];
|
serverHostsList?: string[];
|
||||||
commands: ApplicationCommand[];
|
databaseNamesRegex?: string;
|
||||||
virtualReferences: VirtualReferenceDefinition[];
|
databaseNamesList?: string[];
|
||||||
dictionaryDescriptions: DictionaryDescriptionDefinition[];
|
tableNamesRegex?: string;
|
||||||
|
tableNamesList?: string[];
|
||||||
|
columnNamesRegex?: string;
|
||||||
|
columnNamesList?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApplicationDefinition {
|
||||||
|
appid: string;
|
||||||
|
applicationName: string;
|
||||||
|
applicationIcon?: string;
|
||||||
|
applicationColor?: string;
|
||||||
|
usageRules?: ApplicationUsageRule[];
|
||||||
|
files?: {
|
||||||
|
[key: string]: {
|
||||||
|
label: string;
|
||||||
|
sql: string;
|
||||||
|
type: 'query' | 'command';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
virtualReferences?: VirtualReferenceDefinition[];
|
||||||
|
dictionaryDescriptions?: DictionaryDescriptionDefinition[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
installNewVolatileConnectionListener,
|
installNewVolatileConnectionListener,
|
||||||
refreshPublicCloudFiles,
|
refreshPublicCloudFiles,
|
||||||
} from './utility/api';
|
} from './utility/api';
|
||||||
import { getConfig, getSettings, getUsedApps } from './utility/metadataLoaders';
|
import { getAllApps, getConfig, getSettings } 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';
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
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 getAllApps();
|
||||||
const loadedApiValue = !!(settings && connections && config && apps);
|
const loadedApiValue = !!(settings && connections && config && apps);
|
||||||
|
|
||||||
if (loadedApiValue) {
|
if (loadedApiValue) {
|
||||||
|
|||||||
35
packages/web/src/admin/FolderPermissionChooser.svelte
Normal file
35
packages/web/src/admin/FolderPermissionChooser.svelte
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang='ts'>
|
||||||
|
import PermissionCheckBox from './PermissionCheckBox.svelte';
|
||||||
|
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
||||||
|
|
||||||
|
const { values } = getFormContext();
|
||||||
|
|
||||||
|
export let onSetPermission;
|
||||||
|
export let label;
|
||||||
|
export let folder;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<PermissionCheckBox
|
||||||
|
{label}
|
||||||
|
permission={`files/${folder}/*`}
|
||||||
|
permissions={$values.permissions}
|
||||||
|
basePermissions={$values.basePermissions}
|
||||||
|
{onSetPermission}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="ml-4">
|
||||||
|
<PermissionCheckBox
|
||||||
|
label="Read"
|
||||||
|
permission={`files/${folder}/read`}
|
||||||
|
permissions={$values.permissions}
|
||||||
|
basePermissions={$values.basePermissions}
|
||||||
|
{onSetPermission}
|
||||||
|
/>
|
||||||
|
<PermissionCheckBox
|
||||||
|
label="Write"
|
||||||
|
permission={`files/${folder}/write`}
|
||||||
|
permissions={$values.permissions}
|
||||||
|
basePermissions={$values.basePermissions}
|
||||||
|
{onSetPermission}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
export let filter = null;
|
export let filter = null;
|
||||||
export let disableHover = false;
|
export let disableHover = false;
|
||||||
export let divProps = {};
|
export let divProps = {};
|
||||||
|
export let additionalIcons = null;
|
||||||
|
|
||||||
$: isChecked =
|
$: isChecked =
|
||||||
checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x));
|
checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x));
|
||||||
@@ -160,6 +161,11 @@
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if additionalIcons}
|
||||||
|
{#each additionalIcons as ic}
|
||||||
|
<FontIcon icon={ic.icon} title={ic.title} colorClass={ic.colorClass} />
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
{#if extInfo}
|
{#if extInfo}
|
||||||
<span class="ext-info">
|
<span class="ext-info">
|
||||||
<TokenizedFilteredText text={extInfo} {filter} />
|
<TokenizedFilteredText text={extInfo} {filter} />
|
||||||
|
|||||||
@@ -130,7 +130,7 @@
|
|||||||
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 { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
|
import { getDatabaseList, useAllApps } 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';
|
||||||
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
||||||
@@ -383,7 +383,7 @@
|
|||||||
$currentDatabase,
|
$currentDatabase,
|
||||||
$apps,
|
$apps,
|
||||||
$openedSingleDatabaseConnections,
|
$openedSingleDatabaseConnections,
|
||||||
data.databasePermissionRole,
|
data.databasePermissionRole
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: apps = useUsedApps();
|
$: apps = useAllApps();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AppObjectCore
|
<AppObjectCore
|
||||||
|
|||||||
@@ -405,9 +405,25 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCreateNewApp = () => {
|
||||||
|
showModal(InputTextModal, {
|
||||||
|
header: 'New application',
|
||||||
|
label: 'Application name',
|
||||||
|
value: _.startCase(name),
|
||||||
|
onConfirm: async appName => {
|
||||||
|
const newAppId = await apiCall('apps/create-app-from-db', {
|
||||||
|
appName,
|
||||||
|
server: connection?.server,
|
||||||
|
database: name,
|
||||||
|
});
|
||||||
|
openApplicationEditor(newAppId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const driver = findEngineDriver(connection, getExtensions());
|
const driver = findEngineDriver(connection, getExtensions());
|
||||||
|
|
||||||
const commands = _.flatten((apps || []).map(x => x.commands || []));
|
const commands = _.flatten((apps || []).map(x => Object.values(x.files || {}).filter(x => x.type == 'command')));
|
||||||
|
|
||||||
const isSqlOrDoc =
|
const isSqlOrDoc =
|
||||||
driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document');
|
driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document');
|
||||||
@@ -564,11 +580,26 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
text: _t('database.dataDeployer', { defaultMessage: 'Data deployer' }),
|
text: _t('database.dataDeployer', { defaultMessage: 'Data deployer' }),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isProApp() &&
|
||||||
|
hasPermission(`files/apps/write`) && {
|
||||||
|
onClick: handleCreateNewApp,
|
||||||
|
text: _t('database.createNewApplication', { defaultMessage: 'Create new application' }),
|
||||||
|
},
|
||||||
|
|
||||||
|
isProApp() &&
|
||||||
|
apps?.length > 0 && {
|
||||||
|
text: _t('database.editApplications', { defaultMessage: 'Edit application' }),
|
||||||
|
submenu: apps.map((app: any) => ({
|
||||||
|
text: app.applicationName,
|
||||||
|
onClick: () => openApplicationEditor(app.appid),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
|
|
||||||
commands.length > 0 && [
|
commands.length > 0 && [
|
||||||
commands.map((cmd: any) => ({
|
commands.map((cmd: any) => ({
|
||||||
text: cmd.name,
|
text: cmd.label,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
showModal(ConfirmSqlModal, {
|
showModal(ConfirmSqlModal, {
|
||||||
sql: cmd.sql,
|
sql: cmd.sql,
|
||||||
@@ -618,12 +649,12 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
getConnectionLabel,
|
getConnectionLabel,
|
||||||
} from 'dbgate-tools';
|
} from 'dbgate-tools';
|
||||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||||
import { getDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
|
import { getDatabaseInfo, useAllApps, useDatabaseInfoPeek } from '../utility/metadataLoaders';
|
||||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||||
import ConfirmSqlModal, { runOperationOnDatabase, saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
|
import ConfirmSqlModal, { runOperationOnDatabase, saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
|
||||||
import { filterAppsForDatabase } from '../utility/appTools';
|
import { filterAppsForDatabase, openApplicationEditor } from '../utility/appTools';
|
||||||
import newQuery from '../query/newQuery';
|
import newQuery from '../query/newQuery';
|
||||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||||
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
||||||
@@ -639,7 +670,7 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
import { getNumberIcon } from '../icons/FontIcon.svelte';
|
import { getNumberIcon } from '../icons/FontIcon.svelte';
|
||||||
import { getDatabaseClickActionSetting } from '../settings/settingsTools';
|
import { getDatabaseClickActionSetting } from '../settings/settingsTools';
|
||||||
import { _t } from '../translations';
|
import { _t } from '../translations';
|
||||||
import { dataGridRowHeight } from '../datagrid/DataGridRowHeightMeter.svelte';
|
import { tick } from 'svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
export let passProps;
|
export let passProps;
|
||||||
@@ -657,8 +688,13 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: isPinned = !!$pinnedDatabases.find(x => x?.name == data.name && x?.connection?._id == data.connection?._id);
|
$: isPinned = !!$pinnedDatabases.find(x => x?.name == data.name && x?.connection?._id == data.connection?._id);
|
||||||
$: apps = useUsedApps();
|
$: apps = useAllApps();
|
||||||
$: isLoadingSchemas = $loadingSchemaLists[`${data?.connection?._id}::${data?.name}`];
|
$: isLoadingSchemas = $loadingSchemaLists[`${data?.connection?._id}::${data?.name}`];
|
||||||
|
$: dbInfo = useDatabaseInfoPeek({ conid: data?.connection?._id, database: data?.name });
|
||||||
|
|
||||||
|
$: appsForDb = filterAppsForDatabase(data?.connection, data?.name, $apps, $dbInfo);
|
||||||
|
|
||||||
|
// $: console.log('AppsForDB:', data?.name, appsForDb);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AppObjectCore
|
<AppObjectCore
|
||||||
@@ -681,6 +717,13 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
switchCurrentDatabase(data);
|
switchCurrentDatabase(data);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
additionalIcons={appsForDb?.length > 0
|
||||||
|
? appsForDb.map(ic => ({
|
||||||
|
icon: ic.applicationIcon || 'img app',
|
||||||
|
title: ic.applicationName,
|
||||||
|
colorClass: ic.applicationColor ? `color-icon-${ic.applicationColor}` : undefined,
|
||||||
|
}))
|
||||||
|
: null}
|
||||||
on:mousedown={() => {
|
on:mousedown={() => {
|
||||||
$focusedConnectionOrDatabase = { conid: data.connection?._id, database: data.name, connection: data.connection };
|
$focusedConnectionOrDatabase = { conid: data.connection?._id, database: data.name, connection: data.connection };
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -45,16 +45,16 @@
|
|||||||
schedulerEvents: 'icon scheduler-event',
|
schedulerEvents: 'icon scheduler-event',
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultTabs = {
|
// const defaultTabs = {
|
||||||
tables: 'TableDataTab',
|
// tables: 'TableDataTab',
|
||||||
collections: 'CollectionDataTab',
|
// collections: 'CollectionDataTab',
|
||||||
views: 'ViewDataTab',
|
// views: 'ViewDataTab',
|
||||||
matviews: 'ViewDataTab',
|
// matviews: 'ViewDataTab',
|
||||||
queries: 'QueryDataTab',
|
// queries: 'QueryDataTab',
|
||||||
procedures: 'SqlObjectTab',
|
// procedures: 'SqlObjectTab',
|
||||||
functions: 'SqlObjectTab',
|
// functions: 'SqlObjectTab',
|
||||||
triggers: 'SqlObjectTab',
|
// triggers: 'SqlObjectTab',
|
||||||
};
|
// };
|
||||||
|
|
||||||
function createScriptTemplatesSubmenu(objectTypeField) {
|
function createScriptTemplatesSubmenu(objectTypeField) {
|
||||||
return {
|
return {
|
||||||
@@ -741,7 +741,7 @@
|
|||||||
export async function openDatabaseObjectDetail(
|
export async function openDatabaseObjectDetail(
|
||||||
tabComponent,
|
tabComponent,
|
||||||
scriptTemplate,
|
scriptTemplate,
|
||||||
{ schemaName, pureName, conid, database, objectTypeField, defaultActionId, isRawMode },
|
{ schemaName, pureName, conid, database, objectTypeField, defaultActionId, isRawMode, sql },
|
||||||
forceNewTab?,
|
forceNewTab?,
|
||||||
initialData?,
|
initialData?,
|
||||||
icon?,
|
icon?,
|
||||||
@@ -776,6 +776,7 @@
|
|||||||
initialArgs: scriptTemplate ? { scriptTemplate } : null,
|
initialArgs: scriptTemplate ? { scriptTemplate } : null,
|
||||||
defaultActionId,
|
defaultActionId,
|
||||||
isRawMode,
|
isRawMode,
|
||||||
|
sql,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
initialData,
|
initialData,
|
||||||
@@ -797,7 +798,7 @@
|
|||||||
data,
|
data,
|
||||||
{ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}
|
{ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}
|
||||||
) {
|
) {
|
||||||
const { schemaName, pureName, conid, database, objectTypeField } = data;
|
const { schemaName, pureName, conid, database, objectTypeField, sql } = data;
|
||||||
const driver = findEngineDriver(data, getExtensions());
|
const driver = findEngineDriver(data, getExtensions());
|
||||||
|
|
||||||
const activeTab = getActiveTab();
|
const activeTab = getActiveTab();
|
||||||
@@ -843,6 +844,7 @@
|
|||||||
objectTypeField,
|
objectTypeField,
|
||||||
defaultActionId: prefferedAction.defaultActionId,
|
defaultActionId: prefferedAction.defaultActionId,
|
||||||
isRawMode: prefferedAction?.isRawMode ?? false,
|
isRawMode: prefferedAction?.isRawMode ?? false,
|
||||||
|
sql,
|
||||||
},
|
},
|
||||||
forceNewTab,
|
forceNewTab,
|
||||||
prefferedAction?.initialData,
|
prefferedAction?.initialData,
|
||||||
|
|||||||
@@ -142,6 +142,18 @@
|
|||||||
label: 'Model transform file',
|
label: 'Model transform file',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const apps: FileTypeHandler = isProApp()
|
||||||
|
? {
|
||||||
|
icon: 'img app',
|
||||||
|
format: 'json',
|
||||||
|
tabComponent: 'AppEditorTab',
|
||||||
|
folder: 'apps',
|
||||||
|
currentConnection: false,
|
||||||
|
extension: 'json',
|
||||||
|
label: 'Application file',
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
export const SAVED_FILE_HANDLERS = {
|
export const SAVED_FILE_HANDLERS = {
|
||||||
sql,
|
sql,
|
||||||
shell,
|
shell,
|
||||||
@@ -154,6 +166,7 @@
|
|||||||
modtrans,
|
modtrans,
|
||||||
datadeploy,
|
datadeploy,
|
||||||
dbcompare,
|
dbcompare,
|
||||||
|
apps,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const extractKey = data => data.file;
|
export const extractKey = data => data.file;
|
||||||
|
|||||||
@@ -100,4 +100,12 @@ export const defaultDatabaseObjectAppObjectActions = {
|
|||||||
icon: 'img sql-file',
|
icon: 'img sql-file',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
queries: [
|
||||||
|
{
|
||||||
|
label: 'Show query',
|
||||||
|
tab: 'QueryDataTab',
|
||||||
|
defaultActionId: 'showAppQuery',
|
||||||
|
icon: 'img app-query',
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -268,6 +268,23 @@ if (isProApp()) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProApp()) {
|
||||||
|
registerCommand({
|
||||||
|
id: 'new.application',
|
||||||
|
category: 'New',
|
||||||
|
icon: 'img app',
|
||||||
|
name: 'Application',
|
||||||
|
menuName: 'New application',
|
||||||
|
onClick: () => {
|
||||||
|
openNewTab({
|
||||||
|
title: 'Application #',
|
||||||
|
icon: 'img app',
|
||||||
|
tabComponent: 'AppEditorTab',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'new.diagram',
|
id: 'new.diagram',
|
||||||
category: 'New',
|
category: 'New',
|
||||||
@@ -297,22 +314,22 @@ registerCommand({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
registerCommand({
|
// registerCommand({
|
||||||
id: 'new.application',
|
// id: 'new.application',
|
||||||
category: 'New',
|
// category: 'New',
|
||||||
icon: 'img app',
|
// icon: 'img app',
|
||||||
name: 'Application',
|
// name: 'Application',
|
||||||
onClick: () => {
|
// onClick: () => {
|
||||||
showModal(InputTextModal, {
|
// showModal(InputTextModal, {
|
||||||
value: '',
|
// value: '',
|
||||||
label: 'New application name',
|
// label: 'New application name',
|
||||||
header: 'Create application',
|
// header: 'Create application',
|
||||||
onConfirm: async folder => {
|
// onConfirm: async folder => {
|
||||||
apiCall('apps/create-folder', { folder });
|
// apiCall('apps/create-folder', { folder });
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'new.table',
|
id: 'new.table',
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
import { copyTextToClipboard } from '../utility/clipboard';
|
import { copyTextToClipboard } from '../utility/clipboard';
|
||||||
import VirtualForeignKeyEditorModal from '../tableeditor/VirtualForeignKeyEditorModal.svelte';
|
import VirtualForeignKeyEditorModal from '../tableeditor/VirtualForeignKeyEditorModal.svelte';
|
||||||
import { showModal } from '../modals/modalTools';
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import DefineDictionaryDescriptionModal from '../modals/DefineDictionaryDescriptionModal.svelte';
|
||||||
|
import { sleep } from '../utility/common';
|
||||||
|
|
||||||
export let column;
|
export let column;
|
||||||
export let conid = undefined;
|
export let conid = undefined;
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
export let allowDefineVirtualReferences = false;
|
export let allowDefineVirtualReferences = false;
|
||||||
export let setGrouping;
|
export let setGrouping;
|
||||||
export let seachInColumns = '';
|
export let seachInColumns = '';
|
||||||
|
export let onReload = undefined;
|
||||||
|
|
||||||
const openReferencedTable = () => {
|
const openReferencedTable = () => {
|
||||||
openDatabaseObjectDetail('TableDataTab', null, {
|
openDatabaseObjectDetail('TableDataTab', null, {
|
||||||
@@ -45,6 +48,19 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCustomizeDescriptions = () => {
|
||||||
|
showModal(DefineDictionaryDescriptionModal, {
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
schemaName: column.foreignKey.refSchemaName,
|
||||||
|
pureName: column.foreignKey.refTableName,
|
||||||
|
onConfirm: async () => {
|
||||||
|
await sleep(100);
|
||||||
|
onReload?.();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function getMenu() {
|
function getMenu() {
|
||||||
return [
|
return [
|
||||||
setSort && { onClick: () => setSort('ASC'), text: 'Sort ascending' },
|
setSort && { onClick: () => setSort('ASC'), text: 'Sort ascending' },
|
||||||
@@ -72,10 +88,13 @@
|
|||||||
{ onClick: () => setGrouping('GROUP:DAY'), text: 'Group by DAY' },
|
{ onClick: () => setGrouping('GROUP:DAY'), text: 'Group by DAY' },
|
||||||
],
|
],
|
||||||
|
|
||||||
allowDefineVirtualReferences && [
|
{ divider: true },
|
||||||
{ divider: true },
|
|
||||||
{ onClick: handleDefineVirtualForeignKey, text: 'Define virtual foreign key' },
|
allowDefineVirtualReferences && { onClick: handleDefineVirtualForeignKey, text: 'Define virtual foreign key' },
|
||||||
],
|
column.foreignKey && {
|
||||||
|
onClick: handleCustomizeDescriptions,
|
||||||
|
text: 'Customize description',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2003,6 +2003,7 @@
|
|||||||
grouping={display.getGrouping(col.uniqueName)}
|
grouping={display.getGrouping(col.uniqueName)}
|
||||||
{allowDefineVirtualReferences}
|
{allowDefineVirtualReferences}
|
||||||
seachInColumns={display.config?.searchInColumns}
|
seachInColumns={display.config?.searchInColumns}
|
||||||
|
onReload={refresh}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
import stableStringify from 'json-stable-stringify';
|
import stableStringify from 'json-stable-stringify';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
useAllApps,
|
||||||
useConnectionInfo,
|
useConnectionInfo,
|
||||||
useConnectionList,
|
useConnectionList,
|
||||||
useDatabaseInfo,
|
useDatabaseInfo,
|
||||||
useDatabaseServerVersion,
|
useDatabaseServerVersion,
|
||||||
useServerVersion,
|
useServerVersion,
|
||||||
useSettings,
|
useSettings,
|
||||||
useUsedApps,
|
|
||||||
} from '../utility/metadataLoaders';
|
} from '../utility/metadataLoaders';
|
||||||
|
|
||||||
import DataGrid from './DataGrid.svelte';
|
import DataGrid from './DataGrid.svelte';
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
$: connection = useConnectionInfo({ conid });
|
$: connection = useConnectionInfo({ conid });
|
||||||
$: dbinfo = useDatabaseInfo({ conid, database });
|
$: dbinfo = useDatabaseInfo({ conid, database });
|
||||||
$: serverVersion = useDatabaseServerVersion({ conid, database });
|
$: serverVersion = useDatabaseServerVersion({ conid, database });
|
||||||
$: apps = useUsedApps();
|
$: apps = useAllApps();
|
||||||
$: extendedDbInfo = extendDatabaseInfoFromApps($dbinfo, $apps);
|
$: extendedDbInfo = extendDatabaseInfoFromApps($dbinfo, $apps);
|
||||||
$: connections = useConnectionList();
|
$: connections = useConnectionList();
|
||||||
const settingsValue = useSettings();
|
const settingsValue = useSettings();
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
import DesignerTable from './DesignerTable.svelte';
|
import DesignerTable from './DesignerTable.svelte';
|
||||||
import { isConnectedByReference } from './designerTools';
|
import { isConnectedByReference } from './designerTools';
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
import { getTableInfo, useDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
|
import { getTableInfo, useAllApps, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||||
import cleanupDesignColumns from './cleanupDesignColumns';
|
import cleanupDesignColumns from './cleanupDesignColumns';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
ref => tables.find(x => x.designerId == ref.sourceId) && tables.find(x => x.designerId == ref.targetId)
|
ref => tables.find(x => x.designerId == ref.sourceId) && tables.find(x => x.designerId == ref.targetId)
|
||||||
) as any[];
|
) as any[];
|
||||||
$: zoomKoef = settings?.customizeStyle && value?.style?.zoomKoef ? value?.style?.zoomKoef : 1;
|
$: zoomKoef = settings?.customizeStyle && value?.style?.zoomKoef ? value?.style?.zoomKoef : 1;
|
||||||
$: apps = useUsedApps();
|
$: apps = useAllApps();
|
||||||
|
|
||||||
$: isMultipleTableSelection = tables.filter(x => x.isSelectedTable).length >= 2;
|
$: isMultipleTableSelection = tables.filter(x => x.isSelectedTable).length >= 2;
|
||||||
|
|
||||||
|
|||||||
444
packages/web/src/forms/FormIconField.svelte
Normal file
444
packages/web/src/forms/FormIconField.svelte
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import InlineButton from '../buttons/InlineButton.svelte';
|
||||||
|
import { getFormContext } from './FormProviderCore.svelte';
|
||||||
|
|
||||||
|
export let name;
|
||||||
|
export let label;
|
||||||
|
export let defaultIcon;
|
||||||
|
|
||||||
|
export let templateProps = {};
|
||||||
|
const { template, values, setFieldValue } = getFormContext();
|
||||||
|
|
||||||
|
let showPicker = false;
|
||||||
|
|
||||||
|
// Real-world subject icons for application identification
|
||||||
|
const ICONS = [
|
||||||
|
{ icon: defaultIcon, name: '(Default icon)' },
|
||||||
|
|
||||||
|
// Applications & Tools
|
||||||
|
{ icon: 'mdi mdi-application', name: 'Application' },
|
||||||
|
{ icon: 'mdi mdi-cog', name: 'Settings' },
|
||||||
|
{ icon: 'mdi mdi-tools', name: 'Tools' },
|
||||||
|
{ icon: 'mdi mdi-wrench', name: 'Wrench' },
|
||||||
|
{ icon: 'mdi mdi-hammer', name: 'Hammer' },
|
||||||
|
{ icon: 'mdi mdi-screwdriver', name: 'Screwdriver' },
|
||||||
|
{ icon: 'mdi mdi-palette', name: 'Palette' },
|
||||||
|
{ icon: 'mdi mdi-brush', name: 'Brush' },
|
||||||
|
{ icon: 'mdi mdi-calculator', name: 'Calculator' },
|
||||||
|
|
||||||
|
// Files & Folders
|
||||||
|
{ icon: 'mdi mdi-file', name: 'File' },
|
||||||
|
{ icon: 'mdi mdi-folder', name: 'Folder' },
|
||||||
|
{ icon: 'mdi mdi-folder-open', name: 'Folder Open' },
|
||||||
|
{ icon: 'mdi mdi-file-document', name: 'Document' },
|
||||||
|
{ icon: 'mdi mdi-file-image', name: 'Image File' },
|
||||||
|
{ icon: 'mdi mdi-file-video', name: 'Video File' },
|
||||||
|
{ icon: 'mdi mdi-file-music', name: 'Music File' },
|
||||||
|
{ icon: 'mdi mdi-archive', name: 'Archive' },
|
||||||
|
|
||||||
|
|
||||||
|
// Core Applications
|
||||||
|
{ icon: 'mdi mdi-database', name: 'Database' },
|
||||||
|
{ icon: 'mdi mdi-server', name: 'Server' },
|
||||||
|
{ icon: 'mdi mdi-web', name: 'Web' },
|
||||||
|
{ icon: 'mdi mdi-cloud', name: 'Cloud' },
|
||||||
|
{ icon: 'mdi mdi-monitor', name: 'Monitor' },
|
||||||
|
{ icon: 'mdi mdi-laptop', name: 'Laptop' },
|
||||||
|
{ icon: 'mdi mdi-cellphone', name: 'Mobile' },
|
||||||
|
|
||||||
|
// Business & Finance
|
||||||
|
{ icon: 'mdi mdi-briefcase', name: 'Business' },
|
||||||
|
{ icon: 'mdi mdi-bank', name: 'Banking' },
|
||||||
|
{ icon: 'mdi mdi-currency-usd', name: 'Finance' },
|
||||||
|
{ icon: 'mdi mdi-chart-line', name: 'Analytics' },
|
||||||
|
{ icon: 'mdi mdi-chart-bar', name: 'Reports' },
|
||||||
|
{ icon: 'mdi mdi-chart-pie', name: 'Statistics' },
|
||||||
|
{ icon: 'mdi mdi-calculator', name: 'Calculator' },
|
||||||
|
{ icon: 'mdi mdi-cash-register', name: 'Sales' },
|
||||||
|
{ icon: 'mdi mdi-credit-card', name: 'Payments' },
|
||||||
|
{ icon: 'mdi mdi-receipt', name: 'Invoicing' },
|
||||||
|
|
||||||
|
// Communication & Social
|
||||||
|
{ icon: 'mdi mdi-email', name: 'Email' },
|
||||||
|
{ icon: 'mdi mdi-phone', name: 'Phone' },
|
||||||
|
{ icon: 'mdi mdi-message', name: 'Messaging' },
|
||||||
|
{ icon: 'mdi mdi-chat', name: 'Chat' },
|
||||||
|
{ icon: 'mdi mdi-forum', name: 'Forum' },
|
||||||
|
{ icon: 'mdi mdi-account-group', name: 'Team' },
|
||||||
|
{ icon: 'mdi mdi-bullhorn', name: 'Marketing' },
|
||||||
|
{ icon: 'mdi mdi-newspaper', name: 'News' },
|
||||||
|
|
||||||
|
// Education & Knowledge
|
||||||
|
{ icon: 'mdi mdi-school', name: 'Education' },
|
||||||
|
{ icon: 'mdi mdi-book', name: 'Library' },
|
||||||
|
{ icon: 'mdi mdi-book-open', name: 'Learning' },
|
||||||
|
{ icon: 'mdi mdi-certificate', name: 'Certification' },
|
||||||
|
{ icon: 'mdi mdi-graduation-cap', name: 'Academic' },
|
||||||
|
{ icon: 'mdi mdi-microscope', name: 'Research' },
|
||||||
|
{ icon: 'mdi mdi-flask', name: 'Laboratory' },
|
||||||
|
{ icon: 'mdi mdi-library', name: 'Archive' },
|
||||||
|
|
||||||
|
// Healthcare & Medical
|
||||||
|
{ icon: 'mdi mdi-hospital-building', name: 'Hospital' },
|
||||||
|
{ icon: 'mdi mdi-medical-bag', name: 'Medical' },
|
||||||
|
{ icon: 'mdi mdi-heart-pulse', name: 'Health' },
|
||||||
|
{ icon: 'mdi mdi-pill', name: 'Pharmacy' },
|
||||||
|
{ icon: 'mdi mdi-tooth', name: 'Dental' },
|
||||||
|
{ icon: 'mdi mdi-eye', name: 'Vision' },
|
||||||
|
{ icon: 'mdi mdi-stethoscope', name: 'Clinic' },
|
||||||
|
|
||||||
|
// Transportation & Logistics
|
||||||
|
{ icon: 'mdi mdi-truck', name: 'Logistics' },
|
||||||
|
{ icon: 'mdi mdi-car', name: 'Automotive' },
|
||||||
|
{ icon: 'mdi mdi-airplane', name: 'Aviation' },
|
||||||
|
{ icon: 'mdi mdi-ship-wheel', name: 'Maritime' },
|
||||||
|
{ icon: 'mdi mdi-train', name: 'Railway' },
|
||||||
|
{ icon: 'mdi mdi-bus', name: 'Transit' },
|
||||||
|
{ icon: 'mdi mdi-bike', name: 'Cycling' },
|
||||||
|
{ icon: 'mdi mdi-map', name: 'Navigation' },
|
||||||
|
{ icon: 'mdi mdi-gas-station', name: 'Fuel' },
|
||||||
|
|
||||||
|
// Real Estate & Construction
|
||||||
|
{ icon: 'mdi mdi-home', name: 'Real Estate' },
|
||||||
|
{ icon: 'mdi mdi-office-building', name: 'Commercial' },
|
||||||
|
{ icon: 'mdi mdi-factory', name: 'Industrial' },
|
||||||
|
{ icon: 'mdi mdi-hammer', name: 'Construction' },
|
||||||
|
{ icon: 'mdi mdi-wrench', name: 'Maintenance' },
|
||||||
|
{ icon: 'mdi mdi-tools', name: 'Tools' },
|
||||||
|
{ icon: 'mdi mdi-city', name: 'Urban Planning' },
|
||||||
|
|
||||||
|
// Retail & E-commerce
|
||||||
|
{ icon: 'mdi mdi-store', name: 'Retail' },
|
||||||
|
{ icon: 'mdi mdi-shopping', name: 'Shopping' },
|
||||||
|
{ icon: 'mdi mdi-cart', name: 'E-commerce' },
|
||||||
|
{ icon: 'mdi mdi-barcode', name: 'Inventory' },
|
||||||
|
{ icon: 'mdi mdi-package-variant', name: 'Shipping' },
|
||||||
|
{ icon: 'mdi mdi-gift', name: 'Gifts' },
|
||||||
|
|
||||||
|
// Entertainment & Media
|
||||||
|
{ icon: 'mdi mdi-camera', name: 'Photography' },
|
||||||
|
{ icon: 'mdi mdi-video', name: 'Video' },
|
||||||
|
{ icon: 'mdi mdi-music', name: 'Music' },
|
||||||
|
{ icon: 'mdi mdi-gamepad-variant', name: 'Gaming' },
|
||||||
|
{ icon: 'mdi mdi-movie', name: 'Cinema' },
|
||||||
|
{ icon: 'mdi mdi-television', name: 'Broadcasting' },
|
||||||
|
{ icon: 'mdi mdi-radio', name: 'Radio' },
|
||||||
|
{ icon: 'mdi mdi-theater', name: 'Theater' },
|
||||||
|
|
||||||
|
// Food & Hospitality
|
||||||
|
{ icon: 'mdi mdi-food', name: 'Food Service' },
|
||||||
|
{ icon: 'mdi mdi-coffee', name: 'Cafe' },
|
||||||
|
{ icon: 'mdi mdi-silverware-fork-knife', name: 'Restaurant' },
|
||||||
|
{ icon: 'mdi mdi-pizza', name: 'Pizza' },
|
||||||
|
{ icon: 'mdi mdi-cake', name: 'Bakery' },
|
||||||
|
{ icon: 'mdi mdi-glass-wine', name: 'Bar' },
|
||||||
|
{ icon: 'mdi mdi-bed', name: 'Hotel' },
|
||||||
|
|
||||||
|
// Sports & Fitness
|
||||||
|
{ icon: 'mdi mdi-dumbbell', name: 'Fitness' },
|
||||||
|
{ icon: 'mdi mdi-basketball', name: 'Basketball' },
|
||||||
|
{ icon: 'mdi mdi-soccer', name: 'Soccer' },
|
||||||
|
{ icon: 'mdi mdi-tennis', name: 'Tennis' },
|
||||||
|
{ icon: 'mdi mdi-golf', name: 'Golf' },
|
||||||
|
{ icon: 'mdi mdi-run', name: 'Running' },
|
||||||
|
{ icon: 'mdi mdi-swim', name: 'Swimming' },
|
||||||
|
{ icon: 'mdi mdi-yoga', name: 'Yoga' },
|
||||||
|
|
||||||
|
// Nature & Environment
|
||||||
|
{ icon: 'mdi mdi-tree', name: 'Forestry' },
|
||||||
|
{ icon: 'mdi mdi-flower', name: 'Gardening' },
|
||||||
|
{ icon: 'mdi mdi-leaf', name: 'Environment' },
|
||||||
|
{ icon: 'mdi mdi-weather-sunny', name: 'Weather' },
|
||||||
|
{ icon: 'mdi mdi-earth', name: 'Geography' },
|
||||||
|
{ icon: 'mdi mdi-water', name: 'Water' },
|
||||||
|
{ icon: 'mdi mdi-fire', name: 'Energy' },
|
||||||
|
{ icon: 'mdi mdi-lightning-bolt', name: 'Power' },
|
||||||
|
|
||||||
|
// Science & Technology
|
||||||
|
{ icon: 'mdi mdi-rocket', name: 'Aerospace' },
|
||||||
|
{ icon: 'mdi mdi-atom', name: 'Physics' },
|
||||||
|
{ icon: 'mdi mdi-dna', name: 'Genetics' },
|
||||||
|
{ icon: 'mdi mdi-telescope', name: 'Astronomy' },
|
||||||
|
{ icon: 'mdi mdi-robot', name: 'Robotics' },
|
||||||
|
{ icon: 'mdi mdi-chip', name: 'Electronics' },
|
||||||
|
|
||||||
|
// Security & Safety
|
||||||
|
{ icon: 'mdi mdi-shield', name: 'Security' },
|
||||||
|
{ icon: 'mdi mdi-lock', name: 'Access Control' },
|
||||||
|
{ icon: 'mdi mdi-key', name: 'Authentication' },
|
||||||
|
{ icon: 'mdi mdi-fire-truck', name: 'Emergency' },
|
||||||
|
{ icon: 'mdi mdi-police-badge', name: 'Law Enforcement' },
|
||||||
|
|
||||||
|
// Time & Scheduling
|
||||||
|
{ icon: 'mdi mdi-calendar', name: 'Calendar' },
|
||||||
|
{ icon: 'mdi mdi-clock', name: 'Time Tracking' },
|
||||||
|
{ icon: 'mdi mdi-timer', name: 'Timer' },
|
||||||
|
{ icon: 'mdi mdi-alarm', name: 'Reminders' },
|
||||||
|
|
||||||
|
// Creative & Design
|
||||||
|
{ icon: 'mdi mdi-palette', name: 'Design' },
|
||||||
|
{ icon: 'mdi mdi-brush', name: 'Art' },
|
||||||
|
{ icon: 'mdi mdi-draw', name: 'Drawing' },
|
||||||
|
{ icon: 'mdi mdi-image', name: 'Graphics' },
|
||||||
|
{ icon: 'mdi mdi-format-paint', name: 'Painting' },
|
||||||
|
|
||||||
|
// Alpha Icons
|
||||||
|
{ icon: 'mdi mdi-alpha-a-circle', name: 'A' },
|
||||||
|
{ icon: 'mdi mdi-alpha-b-circle', name: 'B' },
|
||||||
|
{ icon: 'mdi mdi-alpha-c-circle', name: 'C' },
|
||||||
|
{ icon: 'mdi mdi-alpha-d-circle', name: 'D' },
|
||||||
|
{ icon: 'mdi mdi-alpha-e-circle', name: 'E' },
|
||||||
|
{ icon: 'mdi mdi-alpha-f-circle', name: 'F' },
|
||||||
|
{ icon: 'mdi mdi-alpha-g-circle', name: 'G' },
|
||||||
|
{ icon: 'mdi mdi-alpha-h-circle', name: 'H' },
|
||||||
|
{ icon: 'mdi mdi-alpha-i-circle', name: 'I' },
|
||||||
|
{ icon: 'mdi mdi-alpha-j-circle', name: 'J' },
|
||||||
|
{ icon: 'mdi mdi-alpha-k-circle', name: 'K' },
|
||||||
|
{ icon: 'mdi mdi-alpha-l-circle', name: 'L' },
|
||||||
|
{ icon: 'mdi mdi-alpha-m-circle', name: 'M' },
|
||||||
|
{ icon: 'mdi mdi-alpha-n-circle', name: 'N' },
|
||||||
|
{ icon: 'mdi mdi-alpha-o-circle', name: 'O' },
|
||||||
|
{ icon: 'mdi mdi-alpha-p-circle', name: 'P' },
|
||||||
|
{ icon: 'mdi mdi-alpha-q-circle', name: 'Q' },
|
||||||
|
{ icon: 'mdi mdi-alpha-r-circle', name: 'R' },
|
||||||
|
{ icon: 'mdi mdi-alpha-s-circle', name: 'S' },
|
||||||
|
{ icon: 'mdi mdi-alpha-t-circle', name: 'T' },
|
||||||
|
{ icon: 'mdi mdi-alpha-u-circle', name: 'U' },
|
||||||
|
{ icon: 'mdi mdi-alpha-v-circle', name: 'V' },
|
||||||
|
{ icon: 'mdi mdi-alpha-w-circle', name: 'W' },
|
||||||
|
{ icon: 'mdi mdi-alpha-x-circle', name: 'X' },
|
||||||
|
{ icon: 'mdi mdi-alpha-y-circle', name: 'Y' },
|
||||||
|
{ icon: 'mdi mdi-alpha-z-circle', name: 'Z' },
|
||||||
|
|
||||||
|
// Numeric Icons
|
||||||
|
{ icon: 'mdi mdi-numeric-0-circle', name: '0' },
|
||||||
|
{ icon: 'mdi mdi-numeric-1-circle', name: '1' },
|
||||||
|
{ icon: 'mdi mdi-numeric-2-circle', name: '2' },
|
||||||
|
{ icon: 'mdi mdi-numeric-3-circle', name: '3' },
|
||||||
|
{ icon: 'mdi mdi-numeric-4-circle', name: '4' },
|
||||||
|
{ icon: 'mdi mdi-numeric-5-circle', name: '5' },
|
||||||
|
{ icon: 'mdi mdi-numeric-6-circle', name: '6' },
|
||||||
|
{ icon: 'mdi mdi-numeric-7-circle', name: '7' },
|
||||||
|
{ icon: 'mdi mdi-numeric-8-circle', name: '8' },
|
||||||
|
{ icon: 'mdi mdi-numeric-9-circle', name: '9' },
|
||||||
|
{ icon: 'mdi mdi-numeric-10-circle', name: '10' },
|
||||||
|
|
||||||
|
// Alpha Outline Icons
|
||||||
|
{ icon: 'mdi mdi-alpha-a-circle-outline', name: 'A Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-b-circle-outline', name: 'B Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-c-circle-outline', name: 'C Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-d-circle-outline', name: 'D Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-e-circle-outline', name: 'E Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-f-circle-outline', name: 'F Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-g-circle-outline', name: 'G Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-h-circle-outline', name: 'H Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-i-circle-outline', name: 'I Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-j-circle-outline', name: 'J Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-k-circle-outline', name: 'K Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-l-circle-outline', name: 'L Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-m-circle-outline', name: 'M Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-n-circle-outline', name: 'N Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-o-circle-outline', name: 'O Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-p-circle-outline', name: 'P Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-q-circle-outline', name: 'Q Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-r-circle-outline', name: 'R Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-s-circle-outline', name: 'S Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-t-circle-outline', name: 'T Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-u-circle-outline', name: 'U Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-v-circle-outline', name: 'V Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-w-circle-outline', name: 'W Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-x-circle-outline', name: 'X Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-y-circle-outline', name: 'Y Outline' },
|
||||||
|
{ icon: 'mdi mdi-alpha-z-circle-outline', name: 'Z Outline' },
|
||||||
|
|
||||||
|
// Numeric Outline Icons
|
||||||
|
{ icon: 'mdi mdi-numeric-0-circle-outline', name: '0 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-1-circle-outline', name: '1 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-2-circle-outline', name: '2 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-3-circle-outline', name: '3 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-4-circle-outline', name: '4 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-5-circle-outline', name: '5 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-6-circle-outline', name: '6 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-7-circle-outline', name: '7 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-8-circle-outline', name: '8 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-9-circle-outline', name: '9 Outline' },
|
||||||
|
{ icon: 'mdi mdi-numeric-10-circle-outline', name: '10 Outline' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function selectIcon(iconName) {
|
||||||
|
setFieldValue(name, iconName);
|
||||||
|
showPicker = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePicker() {
|
||||||
|
showPicker = !showPicker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(event, action) {
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
event.preventDefault();
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: iconValue = $values?.[name];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:component this={template} type="select" {label} {...templateProps}>
|
||||||
|
<div class="icon-field-container">
|
||||||
|
<div
|
||||||
|
class="selected-icon"
|
||||||
|
on:click={togglePicker}
|
||||||
|
on:keydown={e => handleKeydown(e, togglePicker)}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<FontIcon icon={iconValue || defaultIcon} />
|
||||||
|
<span class="icon-name">{ICONS.find(icon => icon.icon === iconValue)?.name || '(Default icon)'}</span>
|
||||||
|
<FontIcon icon="icon chevron-down" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if showPicker}
|
||||||
|
<div class="icon-picker">
|
||||||
|
<div class="icon-picker-header">
|
||||||
|
<span>Choose an icon</span>
|
||||||
|
<InlineButton on:click={togglePicker}>
|
||||||
|
<FontIcon icon="icon close" />
|
||||||
|
</InlineButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="icon-grid">
|
||||||
|
{#each ICONS as { icon, name: iconDisplayName }}
|
||||||
|
<div
|
||||||
|
class="icon-option"
|
||||||
|
class:selected={iconValue === icon}
|
||||||
|
on:click={() => selectIcon(icon)}
|
||||||
|
on:keydown={e => handleKeydown(e, () => selectIcon(icon))}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
title={iconDisplayName}
|
||||||
|
>
|
||||||
|
<FontIcon {icon} />
|
||||||
|
<span class="icon-label">{iconDisplayName}</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</svelte:component>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--theme-font-1);
|
||||||
|
}
|
||||||
|
.icon-field-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid var(--theme-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--theme-bg-0);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-icon:hover {
|
||||||
|
border-color: var(--theme-border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-icon:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--theme-font-link);
|
||||||
|
box-shadow: 0 0 0 2px var(--theme-font-link-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-name {
|
||||||
|
flex: 1;
|
||||||
|
color: var(--theme-font-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-picker {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
background: var(--theme-bg-0);
|
||||||
|
border: 1px solid var(--theme-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
max-height: 400px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-picker-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-bottom: 1px solid var(--theme-border);
|
||||||
|
background: var(--theme-bg-1);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||||
|
gap: 1px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-option:hover {
|
||||||
|
background: var(--theme-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-option.selected {
|
||||||
|
background: var(--theme-bg-selected);
|
||||||
|
color: var(--theme-font-link);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-option:focus {
|
||||||
|
outline: none;
|
||||||
|
background: var(--theme-bg-hover);
|
||||||
|
box-shadow: 0 0 0 2px var(--theme-font-link-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--theme-font-2);
|
||||||
|
line-height: 1.2;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-option.selected .icon-label {
|
||||||
|
color: var(--theme-font-link);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
value={$values[name] ?? defaultValue}
|
value={$values?.[name] ?? defaultValue}
|
||||||
on:input={e => setFieldValue(name, e.target['value'])}
|
on:input={e => setFieldValue(name, e.target['value'])}
|
||||||
on:input={e => {
|
on:input={e => {
|
||||||
if (saveOnInput) {
|
if (saveOnInput) {
|
||||||
|
|||||||
@@ -1,48 +1,78 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher, tick } from 'svelte';
|
||||||
|
|
||||||
import SelectField from '../forms/SelectField.svelte';
|
import SelectField from '../forms/SelectField.svelte';
|
||||||
import { currentDatabase } from '../stores';
|
import { currentDatabase } from '../stores';
|
||||||
import { filterAppsForDatabase } from '../utility/appTools';
|
import { filterAppsForDatabase } from '../utility/appTools';
|
||||||
import { useAppFolders, useUsedApps } from '../utility/metadataLoaders';
|
import { getConnectionInfo, useAllApps, useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||||
|
import InlineButton from '../buttons/InlineButton.svelte';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
export let value = '#new';
|
export let value = '';
|
||||||
export let disableInitialize = false;
|
export let conid;
|
||||||
|
export let database;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
let selectFieldKey = 0;
|
||||||
|
|
||||||
$: appFolders = useAppFolders();
|
$: dbInfo = useDatabaseInfo({ conid, database });
|
||||||
$: usedApps = useUsedApps();
|
$: connectionInfo = useConnectionInfo({ conid });
|
||||||
|
|
||||||
$: {
|
$: allApps = useAllApps();
|
||||||
if (!disableInitialize && value == '#new' && $currentDatabase) {
|
$: apps = filterAppsForDatabase($connectionInfo, database, $allApps || [], $dbInfo);
|
||||||
const filtered = filterAppsForDatabase($currentDatabase.connection, $currentDatabase.name, $usedApps || []);
|
|
||||||
const common = _.intersection(
|
$: if (apps?.length == 1) {
|
||||||
($appFolders || []).map(x => x.name),
|
value = apps[0].appid;
|
||||||
filtered.map(x => x.name)
|
selectFieldKey++;
|
||||||
);
|
dispatch('change', value);
|
||||||
if (common.length > 0) {
|
}
|
||||||
value = common[0] as string;
|
|
||||||
|
async function handleAddNewApplication() {
|
||||||
|
showModal(InputTextModal, {
|
||||||
|
header: 'New application',
|
||||||
|
label: 'Application name',
|
||||||
|
value: _.startCase(database),
|
||||||
|
onConfirm: async appName => {
|
||||||
|
const newAppId = await apiCall('apps/create-app-from-db', {
|
||||||
|
appName,
|
||||||
|
server: $connectionInfo?.server,
|
||||||
|
database,
|
||||||
|
});
|
||||||
|
await tick();
|
||||||
|
value = newAppId;
|
||||||
dispatch('change', value);
|
dispatch('change', value);
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SelectField
|
<div class="flex">
|
||||||
isNative
|
{#key selectFieldKey}
|
||||||
{...$$restProps}
|
<SelectField
|
||||||
{value}
|
isNative
|
||||||
on:change={e => {
|
{...$$restProps}
|
||||||
value = e.detail;
|
{value}
|
||||||
dispatch('change', value);
|
on:change={e => {
|
||||||
}}
|
value = e.detail;
|
||||||
options={[
|
dispatch('change', value);
|
||||||
{ label: '(New application linked to current DB)', value: '#new' },
|
}}
|
||||||
...($appFolders || []).map(app => ({
|
options={[
|
||||||
label: app.name,
|
{
|
||||||
value: app.name,
|
label: '(not selected)',
|
||||||
})),
|
value: '',
|
||||||
]}
|
},
|
||||||
/>
|
...(apps || []).map(app => ({
|
||||||
|
label: app.applicationName,
|
||||||
|
value: app.appid,
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
<InlineButton on:click={handleAddNewApplication} square>
|
||||||
|
<FontIcon icon="icon plus-thick" padLeft padRight />
|
||||||
|
</InlineButton>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
'icon send': 'mdi mdi-send',
|
'icon send': 'mdi mdi-send',
|
||||||
'icon regex': 'mdi mdi-regex',
|
'icon regex': 'mdi mdi-regex',
|
||||||
'icon list': 'mdi mdi-format-list-bulleted-triangle',
|
'icon list': 'mdi mdi-format-list-bulleted-triangle',
|
||||||
|
'icon help': 'mdi mdi-help',
|
||||||
|
|
||||||
'icon window-restore': 'mdi mdi-window-restore',
|
'icon window-restore': 'mdi mdi-window-restore',
|
||||||
'icon window-maximize': 'mdi mdi-window-maximize',
|
'icon window-maximize': 'mdi mdi-window-maximize',
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FormProvider from '../forms/FormProvider.svelte';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||||
import ModalBase from './ModalBase.svelte';
|
import ModalBase from './ModalBase.svelte';
|
||||||
import { closeCurrentModal } from './modalTools';
|
import { closeCurrentModal } from './modalTools';
|
||||||
import { useAppFolders, useConnectionList, useTableInfo, useUsedApps } from '../utility/metadataLoaders';
|
import { useAllApps, useConnectionList, useTableInfo } from '../utility/metadataLoaders';
|
||||||
import TableControl from '../elements/TableControl.svelte';
|
import TableControl from '../elements/TableControl.svelte';
|
||||||
import TextField from '../forms/TextField.svelte';
|
import TextField from '../forms/TextField.svelte';
|
||||||
import FormTextField from '../forms/FormTextField.svelte';
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
@@ -16,14 +15,12 @@
|
|||||||
checkDescriptionExpression,
|
checkDescriptionExpression,
|
||||||
getDictionaryDescription,
|
getDictionaryDescription,
|
||||||
parseDelimitedColumnList,
|
parseDelimitedColumnList,
|
||||||
saveDictionaryDescription,
|
|
||||||
} from '../utility/dictionaryDescriptionTools';
|
} from '../utility/dictionaryDescriptionTools';
|
||||||
import { includes } from 'lodash';
|
|
||||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
|
||||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||||
import TargetApplicationSelect from '../forms/TargetApplicationSelect.svelte';
|
import TargetApplicationSelect from '../forms/TargetApplicationSelect.svelte';
|
||||||
import { currentDatabase } from '../stores';
|
import { currentDatabase } from '../stores';
|
||||||
import { filterAppsForDatabase } from '../utility/appTools';
|
import { filterAppsForDatabase } from '../utility/appTools';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
export let conid;
|
export let conid;
|
||||||
export let database;
|
export let database;
|
||||||
@@ -33,13 +30,12 @@
|
|||||||
|
|
||||||
$: tableInfo = useTableInfo({ conid, database, schemaName, pureName });
|
$: tableInfo = useTableInfo({ conid, database, schemaName, pureName });
|
||||||
|
|
||||||
$: apps = useUsedApps();
|
$: apps = useAllApps();
|
||||||
$: appFolders = useAppFolders();
|
|
||||||
$: connections = useConnectionList();
|
$: connections = useConnectionList();
|
||||||
|
|
||||||
$: descriptionInfo = getDictionaryDescription($tableInfo, conid, database, $apps, $connections, true);
|
$: descriptionInfo = getDictionaryDescription($tableInfo, conid, database, $apps, $connections, true);
|
||||||
|
|
||||||
const values = writable({ targetApplication: '#new' } as any);
|
const values = writable({ targetApplication: '' } as any);
|
||||||
|
|
||||||
function initValues(descriptionInfo) {
|
function initValues(descriptionInfo) {
|
||||||
$values = {
|
$values = {
|
||||||
@@ -52,28 +48,21 @@
|
|||||||
$: {
|
$: {
|
||||||
if (descriptionInfo) initValues(descriptionInfo);
|
if (descriptionInfo) initValues(descriptionInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
|
||||||
if ($values.targetApplication == '#new' && $currentDatabase) {
|
|
||||||
const filtered = filterAppsForDatabase($currentDatabase.connection, $currentDatabase.name, $apps || []);
|
|
||||||
const common = _.intersection(
|
|
||||||
($appFolders || []).map(x => x.name),
|
|
||||||
filtered.map(x => x.name)
|
|
||||||
);
|
|
||||||
if (common.length > 0) {
|
|
||||||
$values = {
|
|
||||||
...$values,
|
|
||||||
targetApplication: common[0],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormProviderCore {values}>
|
<FormProviderCore {values}>
|
||||||
<ModalBase {...$$restProps}>
|
<ModalBase {...$$restProps}>
|
||||||
<svelte:fragment slot="header">Define description</svelte:fragment>
|
<svelte:fragment slot="header">Define description</svelte:fragment>
|
||||||
|
|
||||||
|
<FormSelectField
|
||||||
|
label="Target application (mandatory)"
|
||||||
|
name="targetApplication"
|
||||||
|
disableInitialize
|
||||||
|
selectFieldComponent={TargetApplicationSelect}
|
||||||
|
{conid}
|
||||||
|
{database}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<TableControl
|
<TableControl
|
||||||
rows={$tableInfo?.columns || []}
|
rows={$tableInfo?.columns || []}
|
||||||
@@ -103,30 +92,34 @@
|
|||||||
|
|
||||||
<FormTextField name="delimiter" label="Delimiter" />
|
<FormTextField name="delimiter" label="Delimiter" />
|
||||||
|
|
||||||
<FormSelectField
|
|
||||||
label="Target application"
|
|
||||||
name="targetApplication"
|
|
||||||
disableInitialize
|
|
||||||
selectFieldComponent={TargetApplicationSelect}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <FormCheckboxField name="useForAllDatabases" label="Use for all databases" /> -->
|
<!-- <FormCheckboxField name="useForAllDatabases" label="Use for all databases" /> -->
|
||||||
|
|
||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
<FormSubmit
|
<FormSubmit
|
||||||
value="OK"
|
value="OK"
|
||||||
disabled={!checkDescriptionExpression($values?.columns, $tableInfo)}
|
disabled={!checkDescriptionExpression($values?.columns, $tableInfo) || !$values.targetApplication}
|
||||||
on:click={() => {
|
on:click={async () => {
|
||||||
closeCurrentModal();
|
closeCurrentModal();
|
||||||
saveDictionaryDescription(
|
|
||||||
$tableInfo,
|
const expression = $values.columns;
|
||||||
conid,
|
await apiCall('apps/save-dictionary-description', {
|
||||||
database,
|
appid: $values.targetApplication,
|
||||||
$values.columns,
|
schemaName: $tableInfo.schemaName,
|
||||||
$values.delimiter,
|
pureName: $tableInfo.pureName,
|
||||||
$values.targetApplication
|
columns: parseDelimitedColumnList(expression),
|
||||||
);
|
expression,
|
||||||
onConfirm();
|
delimiter: $values.delimiter,
|
||||||
|
});
|
||||||
|
|
||||||
|
// saveDictionaryDescription(
|
||||||
|
// $tableInfo,
|
||||||
|
// conid,
|
||||||
|
// database,
|
||||||
|
// $values.columns,
|
||||||
|
// $values.delimiter,
|
||||||
|
// $values.targetApplication
|
||||||
|
// );
|
||||||
|
onConfirm?.();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { closeCurrentModal, showModal } from './modalTools';
|
import { closeCurrentModal, showModal } from './modalTools';
|
||||||
import DefineDictionaryDescriptionModal from './DefineDictionaryDescriptionModal.svelte';
|
import DefineDictionaryDescriptionModal from './DefineDictionaryDescriptionModal.svelte';
|
||||||
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
|
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
|
||||||
import { getTableInfo, useConnectionList, useUsedApps } from '../utility/metadataLoaders';
|
import { getTableInfo, useAllApps, useConnectionList } from '../utility/metadataLoaders';
|
||||||
import { getDictionaryDescription } from '../utility/dictionaryDescriptionTools';
|
import { getDictionaryDescription } from '../utility/dictionaryDescriptionTools';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { dumpSqlSelect } from 'dbgate-sqltree';
|
import { dumpSqlSelect } from 'dbgate-sqltree';
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
let checkedKeys = [];
|
let checkedKeys = [];
|
||||||
|
|
||||||
$: apps = useUsedApps();
|
$: apps = useAllApps();
|
||||||
$: connections = useConnectionList();
|
$: connections = useConnectionList();
|
||||||
|
|
||||||
function defineDescription() {
|
function defineDescription() {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import TargetApplicationSelect from '../forms/TargetApplicationSelect.svelte';
|
import TargetApplicationSelect from '../forms/TargetApplicationSelect.svelte';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import { saveDbToApp } from '../utility/appTools';
|
// import { apiCall } from '../utility/api';
|
||||||
|
// import { saveDbToApp } from '../utility/appTools';
|
||||||
|
|
||||||
export let conid;
|
export let conid;
|
||||||
export let database;
|
export let database;
|
||||||
@@ -173,7 +174,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="label col-3">Target application</div>
|
<div class="label col-3">Target application</div>
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<TargetApplicationSelect bind:value={dstApp} />
|
<TargetApplicationSelect bind:value={dstApp} {conid} {database} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,10 +182,10 @@
|
|||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
<FormSubmit
|
<FormSubmit
|
||||||
value={'Save'}
|
value={'Save'}
|
||||||
|
disabled={!dstApp}
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
const appFolder = await saveDbToApp(conid, database, dstApp);
|
|
||||||
await apiCall('apps/save-virtual-reference', {
|
await apiCall('apps/save-virtual-reference', {
|
||||||
appFolder,
|
appid: dstApp,
|
||||||
schemaName,
|
schemaName,
|
||||||
pureName,
|
pureName,
|
||||||
refSchemaName,
|
refSchemaName,
|
||||||
|
|||||||
@@ -28,6 +28,10 @@
|
|||||||
import { apiCall, apiOff, apiOn } from '../utility/api';
|
import { apiCall, apiOff, apiOn } from '../utility/api';
|
||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||||
import useEffect from '../utility/useEffect';
|
import useEffect from '../utility/useEffect';
|
||||||
|
import { getSqlFrontMatter } from 'dbgate-tools';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
import JslChart from '../charts/JslChart.svelte';
|
||||||
|
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||||
|
|
||||||
export const activator = createActivator('QueryDataTab', true);
|
export const activator = createActivator('QueryDataTab', true);
|
||||||
|
|
||||||
@@ -40,6 +44,8 @@
|
|||||||
let jslid;
|
let jslid;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
|
$: frontMatter = getSqlFrontMatter(sql, yaml);
|
||||||
|
|
||||||
async function loadData(conid, database, sql) {
|
async function loadData(conid, database, sql) {
|
||||||
const resp = await apiCall('sessions/execute-reader', {
|
const resp = await apiCall('sessions/execute-reader', {
|
||||||
conid,
|
conid,
|
||||||
@@ -96,17 +102,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: $effect;
|
$: $effect;
|
||||||
|
|
||||||
|
$: selectedChart = frontMatter?.['selected-chart'];
|
||||||
|
$: fixedChartDefinition = selectedChart && frontMatter ? frontMatter?.[`chart-${selectedChart}`] : null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolStripContainer>
|
<ToolStripContainer>
|
||||||
{#if jslid}
|
{#if loading}
|
||||||
<JslDataGrid {jslid} listenInitializeFile onCustomGridRefresh={handleRefresh} focusOnVisible />
|
|
||||||
{:else}
|
|
||||||
<LoadingInfo message="Loading data..." />
|
<LoadingInfo message="Loading data..." />
|
||||||
|
{:else if jslid}
|
||||||
|
{#if fixedChartDefinition}
|
||||||
|
<JslChart {jslid} fixedDefinition={fixedChartDefinition} />
|
||||||
|
{:else}
|
||||||
|
<JslDataGrid {jslid} listenInitializeFile onCustomGridRefresh={handleRefresh} focusOnVisible />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<svelte:fragment slot="toolstrip">
|
<svelte:fragment slot="toolstrip">
|
||||||
<ToolStripCommandButton command="dataGrid.refresh" />
|
{#if fixedChartDefinition}
|
||||||
|
<ToolStripButton on:click={handleRefresh} icon="icon refresh">Refresh</ToolStripButton>
|
||||||
|
{:else}
|
||||||
|
<ToolStripCommandButton command="dataGrid.refresh" />
|
||||||
|
{/if}
|
||||||
<ToolStripCommandButton command="queryData.stopLoading" />
|
<ToolStripCommandButton command="queryData.stopLoading" />
|
||||||
<ToolStripExportButton command="jslTableGrid.export" {quickExportHandlerRef} />
|
{#if !fixedChartDefinition}
|
||||||
|
<ToolStripExportButton command="jslTableGrid.export" {quickExportHandlerRef} />
|
||||||
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ToolStripContainer>
|
</ToolStripContainer>
|
||||||
|
|||||||
@@ -1,33 +1,131 @@
|
|||||||
import type { ApplicationDefinition, StoredConnection } from 'dbgate-types';
|
import type { ApplicationDefinition, DatabaseInfo, StoredConnection } from 'dbgate-types';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { match } from 'fuzzy';
|
||||||
|
import { getConnectionInfo } from './metadataLoaders';
|
||||||
|
import openNewTab from './openNewTab';
|
||||||
|
|
||||||
export async function saveDbToApp(conid: string, database: string, app: string) {
|
// export async function saveDbToApp(conid: string, database: string, app: string) {
|
||||||
if (app == '#new') {
|
// const connection = await getConnectionInfo({ conid });
|
||||||
const folder = await apiCall('apps/create-folder', { folder: database });
|
|
||||||
|
|
||||||
await apiCall('connections/update-database', {
|
// if (app == '#new') {
|
||||||
conid,
|
// const appJson = {
|
||||||
database,
|
// applicationName: _.startCase(database),
|
||||||
values: {
|
// usageRules: [
|
||||||
[`useApp:${folder}`]: true,
|
// {
|
||||||
},
|
// serverHostsList: connection?.server ? [connection.server] : undefined,
|
||||||
});
|
// databaseNamesList: [database],
|
||||||
|
// conditionGroup: '1',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// };
|
||||||
|
|
||||||
return folder;
|
// const file =
|
||||||
|
|
||||||
|
// const folder = await apiCall('apps/create-folder', { folder: database });
|
||||||
|
|
||||||
|
// await apiCall('connections/update-database', {
|
||||||
|
// conid,
|
||||||
|
// database,
|
||||||
|
// values: {
|
||||||
|
// [`useApp:${folder}`]: true,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return folder;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await apiCall('connections/update-database', {
|
||||||
|
// conid,
|
||||||
|
// database,
|
||||||
|
// values: {
|
||||||
|
// [`useApp:${app}`]: true,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return app;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function filterAppsForDatabase(
|
||||||
|
connection,
|
||||||
|
database: string,
|
||||||
|
apps: ApplicationDefinition[],
|
||||||
|
dbinfo: DatabaseInfo = null
|
||||||
|
): ApplicationDefinition[] {
|
||||||
|
if (!apps) {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
// console.log('ALL APPS:', apps);
|
||||||
await apiCall('connections/update-database', {
|
// console.log('DB INFO:', dbinfo);
|
||||||
conid,
|
// console.log('CONNECTION:', connection);
|
||||||
database,
|
// console.log('DATABASE:', database);
|
||||||
values: {
|
return apps.filter(app => {
|
||||||
[`useApp:${app}`]: true,
|
const groupedConditions = _.groupBy(app.usageRules, rule => rule.conditionGroup || '1');
|
||||||
},
|
for (const group of Object.values(groupedConditions)) {
|
||||||
|
let groupMatch = true;
|
||||||
|
for (const rule of group) {
|
||||||
|
let ruleMatch = true;
|
||||||
|
if (rule.serverHostsRegex) {
|
||||||
|
const re = new RegExp(rule.serverHostsRegex);
|
||||||
|
ruleMatch = ruleMatch && !!connection?.server && re.test(connection.server);
|
||||||
|
}
|
||||||
|
if (rule.serverHostsList) {
|
||||||
|
ruleMatch = ruleMatch && !!connection?.server && rule.serverHostsList.includes(connection.server);
|
||||||
|
}
|
||||||
|
if (rule.databaseNamesRegex) {
|
||||||
|
const re = new RegExp(rule.databaseNamesRegex);
|
||||||
|
ruleMatch = ruleMatch && !!database && re.test(database);
|
||||||
|
}
|
||||||
|
if (rule.databaseNamesList) {
|
||||||
|
ruleMatch = ruleMatch && !!database && rule.databaseNamesList.includes(database);
|
||||||
|
}
|
||||||
|
let matchedTables = dbinfo?.tables;
|
||||||
|
if (rule.tableNamesRegex) {
|
||||||
|
const re = new RegExp(rule.tableNamesRegex);
|
||||||
|
matchedTables = dbinfo?.tables?.filter(table => !!table && re.test(table.pureName)) || [];
|
||||||
|
ruleMatch = ruleMatch && matchedTables.length > 0;
|
||||||
|
}
|
||||||
|
if (rule.tableNamesList) {
|
||||||
|
matchedTables =
|
||||||
|
dbinfo?.tables?.filter(table => !!table && rule.tableNamesList.includes(table.pureName)) || [];
|
||||||
|
ruleMatch = ruleMatch && matchedTables.length > 0;
|
||||||
|
}
|
||||||
|
if (rule.columnNamesRegex) {
|
||||||
|
const re = new RegExp(rule.columnNamesRegex);
|
||||||
|
ruleMatch =
|
||||||
|
ruleMatch &&
|
||||||
|
matchedTables.some(table => !!table?.columns?.some(column => !!column && re.test(column.columnName)));
|
||||||
|
}
|
||||||
|
if (rule.columnNamesList) {
|
||||||
|
ruleMatch =
|
||||||
|
ruleMatch &&
|
||||||
|
matchedTables.some(
|
||||||
|
table => !!table?.columns?.some(column => !!column && rule.columnNamesList.includes(column.columnName))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
groupMatch = groupMatch && ruleMatch;
|
||||||
|
}
|
||||||
|
if (groupMatch) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
// const db = (connection?.databases || []).find(x => x.name == database);
|
||||||
return app;
|
// return apps?.filter(app => db && db[`useApp:${app.name}`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterAppsForDatabase(connection, database: string, $apps): ApplicationDefinition[] {
|
export async function openApplicationEditor(appid) {
|
||||||
const db = (connection?.databases || []).find(x => x.name == database);
|
const dataContent = await apiCall('files/load', { folder: 'apps', file: appid, format: 'json' });
|
||||||
return $apps?.filter(app => db && db[`useApp:${app.name}`]);
|
openNewTab(
|
||||||
|
{
|
||||||
|
title: appid,
|
||||||
|
icon: 'img app',
|
||||||
|
tabComponent: 'AppEditorTab',
|
||||||
|
props: {
|
||||||
|
savedFile: appid,
|
||||||
|
savedFolder: 'apps',
|
||||||
|
savedFormat: 'json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ editor: dataContent }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const cachedByKey = {};
|
|||||||
const cachedPromisesByKey = {};
|
const cachedPromisesByKey = {};
|
||||||
const cachedKeysByReloadTrigger = {};
|
const cachedKeysByReloadTrigger = {};
|
||||||
const subscriptionsByReloadTrigger = {};
|
const subscriptionsByReloadTrigger = {};
|
||||||
|
const subscriptionsByByCacheKeyPeek = {};
|
||||||
const cacheGenerationByKey = {};
|
const cacheGenerationByKey = {};
|
||||||
|
|
||||||
let cacheGeneration = 0;
|
let cacheGeneration = 0;
|
||||||
@@ -29,6 +30,7 @@ function cacheSet(cacheKey, value, reloadTrigger, generation) {
|
|||||||
addCacheKeyToReloadTrigger(cacheKey, reloadTrigger);
|
addCacheKeyToReloadTrigger(cacheKey, reloadTrigger);
|
||||||
delete cachedPromisesByKey[cacheKey];
|
delete cachedPromisesByKey[cacheKey];
|
||||||
cacheGenerationByKey[cacheKey] = generation;
|
cacheGenerationByKey[cacheKey] = generation;
|
||||||
|
dispatchCacheChangePeek(cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cacheClean(reloadTrigger) {
|
function cacheClean(reloadTrigger) {
|
||||||
@@ -64,6 +66,10 @@ function getCacheGenerationForKey(cacheKey) {
|
|||||||
return cacheGenerationByKey[cacheKey] || 0;
|
return cacheGenerationByKey[cacheKey] || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCachedValue(cacheKey) {
|
||||||
|
return cacheGet(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadCachedValue(reloadTrigger, cacheKey, func) {
|
export async function loadCachedValue(reloadTrigger, cacheKey, func) {
|
||||||
const fromCache = cacheGet(cacheKey);
|
const fromCache = cacheGet(cacheKey);
|
||||||
if (fromCache) {
|
if (fromCache) {
|
||||||
@@ -107,12 +113,36 @@ export async function unsubscribeCacheChange(reloadTrigger, cacheKey, reloadHand
|
|||||||
x => x != reloadHandler
|
x => x != reloadHandler
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (subscriptionsByReloadTrigger[itemString].length == 0) {
|
if (subscriptionsByReloadTrigger[itemString]?.length == 0) {
|
||||||
delete subscriptionsByReloadTrigger[itemString];
|
delete subscriptionsByReloadTrigger[itemString];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function subscribeCachePeek(cacheKey, peekHandler) {
|
||||||
|
if (!subscriptionsByByCacheKeyPeek[cacheKey]) {
|
||||||
|
subscriptionsByByCacheKeyPeek[cacheKey] = [];
|
||||||
|
}
|
||||||
|
subscriptionsByByCacheKeyPeek[cacheKey].push(peekHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsubscribeCachePeek(cacheKey, peekHandler) {
|
||||||
|
if (subscriptionsByByCacheKeyPeek[cacheKey]) {
|
||||||
|
subscriptionsByByCacheKeyPeek[cacheKey] = subscriptionsByByCacheKeyPeek[cacheKey].filter(x => x != peekHandler);
|
||||||
|
}
|
||||||
|
if (subscriptionsByByCacheKeyPeek[cacheKey]?.length == 0) {
|
||||||
|
delete subscriptionsByByCacheKeyPeek[cacheKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchCacheChangePeek(cacheKey) {
|
||||||
|
if (subscriptionsByByCacheKeyPeek[cacheKey]) {
|
||||||
|
for (const handler of subscriptionsByByCacheKeyPeek[cacheKey]) {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function dispatchCacheChange(reloadTrigger) {
|
export function dispatchCacheChange(reloadTrigger) {
|
||||||
cacheClean(reloadTrigger);
|
cacheClean(reloadTrigger);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { DictionaryDescription } from 'dbgate-datalib';
|
import type { DictionaryDescription } from 'dbgate-datalib';
|
||||||
import type { ApplicationDefinition, TableInfo } from 'dbgate-types';
|
import type { ApplicationDefinition, DatabaseInfo, TableInfo } from 'dbgate-types';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { apiCall } from './api';
|
import { apiCall } from './api';
|
||||||
import { filterAppsForDatabase, saveDbToApp } from './appTools';
|
import { filterAppsForDatabase } from './appTools';
|
||||||
|
// import { filterAppsForDatabase, saveDbToApp } from './appTools';
|
||||||
|
|
||||||
function checkDescriptionColumns(columns: string[], table: TableInfo) {
|
function checkDescriptionColumns(columns: string[], table: TableInfo) {
|
||||||
if (!columns?.length) return false;
|
if (!columns?.length) return false;
|
||||||
@@ -17,7 +18,8 @@ export function getDictionaryDescription(
|
|||||||
database: string,
|
database: string,
|
||||||
apps: ApplicationDefinition[],
|
apps: ApplicationDefinition[],
|
||||||
connections,
|
connections,
|
||||||
skipCheckSaved: boolean = false
|
skipCheckSaved: boolean = false,
|
||||||
|
dbInfo: DatabaseInfo = null
|
||||||
): DictionaryDescription {
|
): DictionaryDescription {
|
||||||
const conn = connections?.find(x => x._id == conid);
|
const conn = connections?.find(x => x._id == conid);
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ export function getDictionaryDescription(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbApps = filterAppsForDatabase(conn, database, apps);
|
const dbApps = filterAppsForDatabase(conn, database, apps, dbInfo);
|
||||||
|
|
||||||
if (!dbApps) {
|
if (!dbApps) {
|
||||||
return null;
|
return null;
|
||||||
@@ -70,22 +72,20 @@ export function changeDelimitedColumnList(columns, columnName, isChecked) {
|
|||||||
return parsed.join(',');
|
return parsed.join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveDictionaryDescription(
|
// export async function saveDictionaryDescription(
|
||||||
table: TableInfo,
|
// table: TableInfo,
|
||||||
conid: string,
|
// conid: string,
|
||||||
database: string,
|
// database: string,
|
||||||
expression: string,
|
// expression: string,
|
||||||
delimiter: string,
|
// delimiter: string,
|
||||||
targetApplication: string
|
// targetApplication: string
|
||||||
) {
|
// ) {
|
||||||
const appFolder = await saveDbToApp(conid, database, targetApplication);
|
// await apiCall('apps/save-dictionary-description', {
|
||||||
|
// appFolder,
|
||||||
await apiCall('apps/save-dictionary-description', {
|
// schemaName: table.schemaName,
|
||||||
appFolder,
|
// pureName: table.pureName,
|
||||||
schemaName: table.schemaName,
|
// columns: parseDelimitedColumnList(expression),
|
||||||
pureName: table.pureName,
|
// expression,
|
||||||
columns: parseDelimitedColumnList(expression),
|
// delimiter,
|
||||||
expression,
|
// });
|
||||||
delimiter,
|
// }
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { loadCachedValue, subscribeCacheChange, unsubscribeCacheChange } from './cache';
|
import {
|
||||||
|
getCachedValue,
|
||||||
|
loadCachedValue,
|
||||||
|
subscribeCacheChange,
|
||||||
|
subscribeCachePeek,
|
||||||
|
unsubscribeCacheChange,
|
||||||
|
unsubscribeCachePeek,
|
||||||
|
} from './cache';
|
||||||
import stableStringify from 'json-stable-stringify';
|
import stableStringify from 'json-stable-stringify';
|
||||||
import { derived } from 'svelte/store';
|
import { derived } from 'svelte/store';
|
||||||
import { extendDatabaseInfo } from 'dbgate-tools';
|
import { extendDatabaseInfo } from 'dbgate-tools';
|
||||||
@@ -107,17 +114,17 @@ const archiveFilesLoader = ({ folder }) => ({
|
|||||||
reloadTrigger: { key: `archive-files-changed`, folder },
|
reloadTrigger: { key: `archive-files-changed`, folder },
|
||||||
});
|
});
|
||||||
|
|
||||||
const appFoldersLoader = () => ({
|
// const appFoldersLoader = () => ({
|
||||||
url: 'apps/folders',
|
// url: 'apps/folders',
|
||||||
params: {},
|
// params: {},
|
||||||
reloadTrigger: { key: `app-folders-changed` },
|
// reloadTrigger: { key: `app-folders-changed` },
|
||||||
});
|
// });
|
||||||
|
|
||||||
const appFilesLoader = ({ folder }) => ({
|
// const appFilesLoader = ({ folder }) => ({
|
||||||
url: 'apps/files',
|
// url: 'apps/files',
|
||||||
params: { folder },
|
// params: { folder },
|
||||||
reloadTrigger: { key: `app-files-changed`, app: folder },
|
// reloadTrigger: { key: `app-files-changed`, app: folder },
|
||||||
});
|
// });
|
||||||
|
|
||||||
// const dbAppsLoader = ({ conid, database }) => ({
|
// const dbAppsLoader = ({ conid, database }) => ({
|
||||||
// url: 'apps/get-apps-for-db',
|
// url: 'apps/get-apps-for-db',
|
||||||
@@ -125,10 +132,10 @@ const appFilesLoader = ({ folder }) => ({
|
|||||||
// reloadTrigger: `db-apps-changed-${conid}-${database}`,
|
// reloadTrigger: `db-apps-changed-${conid}-${database}`,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const usedAppsLoader = ({ conid, database }) => ({
|
const allAppsLoader = () => ({
|
||||||
url: 'apps/get-used-apps',
|
url: 'apps/get-all-apps',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: { key: `used-apps-changed` },
|
reloadTrigger: { key: `files-changed`, folder: 'apps' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const serverStatusLoader = () => ({
|
const serverStatusLoader = () => ({
|
||||||
@@ -227,6 +234,37 @@ function useCore(loader, args) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useCorePeek(loader, args) {
|
||||||
|
const { url, params, reloadTrigger, transform, onLoaded } = loader(args);
|
||||||
|
const cacheKey = stableStringify({ url, ...params });
|
||||||
|
let openedCount = 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: onChange => {
|
||||||
|
async function handlePeek() {
|
||||||
|
const res = getCachedValue(cacheKey);
|
||||||
|
if (openedCount > 0) {
|
||||||
|
onChange(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openedCount += 1;
|
||||||
|
handlePeek();
|
||||||
|
|
||||||
|
if (reloadTrigger) {
|
||||||
|
subscribeCachePeek(cacheKey, handlePeek);
|
||||||
|
return () => {
|
||||||
|
openedCount -= 1;
|
||||||
|
unsubscribeCachePeek(cacheKey, handlePeek);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return () => {
|
||||||
|
openedCount -= 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** @returns {Promise<import('dbgate-types').DatabaseInfo>} */
|
/** @returns {Promise<import('dbgate-types').DatabaseInfo>} */
|
||||||
export function getDatabaseInfo(args) {
|
export function getDatabaseInfo(args) {
|
||||||
return getCore(databaseInfoLoader, args);
|
return getCore(databaseInfoLoader, args);
|
||||||
@@ -237,6 +275,10 @@ export function useDatabaseInfo(args) {
|
|||||||
return useCore(databaseInfoLoader, args);
|
return useCore(databaseInfoLoader, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useDatabaseInfoPeek(args) {
|
||||||
|
return useCorePeek(databaseInfoLoader, args);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getDbCore(args, objectTypeField = undefined) {
|
export async function getDbCore(args, objectTypeField = undefined) {
|
||||||
const db = await getDatabaseInfo(args);
|
const db = await getDatabaseInfo(args);
|
||||||
if (!db) return null;
|
if (!db) return null;
|
||||||
@@ -392,25 +434,25 @@ export function useArchiveFolders(args = {}) {
|
|||||||
return useCore(archiveFoldersLoader, args);
|
return useCore(archiveFoldersLoader, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAppFiles(args) {
|
// export function getAppFiles(args) {
|
||||||
return getCore(appFilesLoader, args);
|
// return getCore(appFilesLoader, args);
|
||||||
}
|
// }
|
||||||
export function useAppFiles(args) {
|
// export function useAppFiles(args) {
|
||||||
return useCore(appFilesLoader, args);
|
// return useCore(appFilesLoader, args);
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function getAppFolders(args = {}) {
|
// export function getAppFolders(args = {}) {
|
||||||
return getCore(appFoldersLoader, args);
|
// return getCore(appFoldersLoader, args);
|
||||||
}
|
// }
|
||||||
export function useAppFolders(args = {}) {
|
// export function useAppFolders(args = {}) {
|
||||||
return useCore(appFoldersLoader, args);
|
// return useCore(appFoldersLoader, args);
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function getUsedApps(args = {}) {
|
export function getAllApps(args = {}) {
|
||||||
return getCore(usedAppsLoader, args);
|
return getCore(allAppsLoader, args);
|
||||||
}
|
}
|
||||||
export function useUsedApps(args = {}) {
|
export function useAllApps(args = {}) {
|
||||||
return useCore(usedAppsLoader, args);
|
return useCore(allAppsLoader, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function getDbApps(args = {}) {
|
// export function getDbApps(args = {}) {
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
<script lang="ts" context="module">
|
|
||||||
const APP_LABELS = {
|
|
||||||
'command.sql': 'SQL commands',
|
|
||||||
'query.sql': 'SQL queries',
|
|
||||||
};
|
|
||||||
|
|
||||||
const COMMAND_TEMPLATE = `-- Write SQL command here
|
|
||||||
-- After save, you can execute it from database context menu, for all databases, which use this application
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QUERY_TEMPLATE = `-- Write SQL query here
|
|
||||||
-- After save, you can view it in tables list, for all databases, which use this application
|
|
||||||
`;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { createFreeTableModel } from 'dbgate-datalib';
|
|
||||||
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import AppObjectList from '../appobj/AppObjectList.svelte';
|
|
||||||
import * as appFileAppObject from '../appobj/AppFileAppObject.svelte';
|
|
||||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
|
||||||
import DropDownButton from '../buttons/DropDownButton.svelte';
|
|
||||||
|
|
||||||
import InlineButton from '../buttons/InlineButton.svelte';
|
|
||||||
|
|
||||||
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
|
||||||
import SearchInput from '../elements/SearchInput.svelte';
|
|
||||||
import FontIcon from '../icons/FontIcon.svelte';
|
|
||||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
|
||||||
import { showModal } from '../modals/modalTools';
|
|
||||||
import newQuery from '../query/newQuery';
|
|
||||||
import { currentApplication } from '../stores';
|
|
||||||
import { apiCall } from '../utility/api';
|
|
||||||
import { useAppFiles, useArchiveFolders } from '../utility/metadataLoaders';
|
|
||||||
import openNewTab from '../utility/openNewTab';
|
|
||||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
|
||||||
import { showSnackbarError } from '../utility/snackbar';
|
|
||||||
|
|
||||||
let filter = '';
|
|
||||||
|
|
||||||
$: folder = $currentApplication;
|
|
||||||
$: files = useAppFiles({ folder });
|
|
||||||
|
|
||||||
const handleRefreshFiles = () => {
|
|
||||||
apiCall('apps/refresh-files', { folder });
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleNewSqlFile(fileType, header, initialData) {
|
|
||||||
showModal(InputTextModal, {
|
|
||||||
value: '',
|
|
||||||
label: 'New file name',
|
|
||||||
header,
|
|
||||||
onConfirm: async file => {
|
|
||||||
newQuery({
|
|
||||||
title: file,
|
|
||||||
initialData,
|
|
||||||
// @ts-ignore
|
|
||||||
savedFile: file + '.' + fileType,
|
|
||||||
savedFolder: 'app:' + $currentApplication,
|
|
||||||
savedFormat: 'text',
|
|
||||||
appFolder: $currentApplication,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleNewConfigFile(fileName, content) {
|
|
||||||
if (!(await apiCall('apps/create-config-file', { fileName, content, appFolder: $currentApplication }))) {
|
|
||||||
showSnackbarError('File not created, probably already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAddMenu() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
text: 'New SQL command',
|
|
||||||
onClick: () => handleNewSqlFile('command.sql', 'Create new SQL command', COMMAND_TEMPLATE),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'New SQL query',
|
|
||||||
onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query', QUERY_TEMPLATE),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'New virtual references file',
|
|
||||||
onClick: () => handleNewConfigFile('virtual-references.config.json', []),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'New dictionary descriptions file',
|
|
||||||
onClick: () => handleNewConfigFile('dictionary-descriptions.config.json', []),
|
|
||||||
},
|
|
||||||
|
|
||||||
// { text: 'New query view', onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query', QUERY_TEMPLATE) },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<SearchBoxWrapper>
|
|
||||||
<SearchInput placeholder="Search application files" bind:value={filter} />
|
|
||||||
|
|
||||||
<CloseSearchButton bind:filter />
|
|
||||||
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
|
|
||||||
<InlineButton on:click={handleRefreshFiles} title="Refresh files of selected application">
|
|
||||||
<FontIcon icon="icon refresh" />
|
|
||||||
</InlineButton>
|
|
||||||
</SearchBoxWrapper>
|
|
||||||
<WidgetsInnerContainer>
|
|
||||||
<AppObjectList
|
|
||||||
list={($files || []).map(file => ({
|
|
||||||
fileName: file.name,
|
|
||||||
folderName: folder,
|
|
||||||
fileType: file.type,
|
|
||||||
fileLabel: file.label,
|
|
||||||
}))}
|
|
||||||
groupFunc={data => APP_LABELS[data.fileType] || 'App config'}
|
|
||||||
module={appFileAppObject}
|
|
||||||
{filter}
|
|
||||||
/>
|
|
||||||
</WidgetsInnerContainer>
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import AppObjectList from '../appobj/AppObjectList.svelte';
|
|
||||||
import * as appFolderAppObject from '../appobj/AppFolderAppObject.svelte';
|
|
||||||
import runCommand from '../commands/runCommand';
|
|
||||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
|
||||||
|
|
||||||
import InlineButton from '../buttons/InlineButton.svelte';
|
|
||||||
|
|
||||||
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
|
||||||
import SearchInput from '../elements/SearchInput.svelte';
|
|
||||||
import FontIcon from '../icons/FontIcon.svelte';
|
|
||||||
import { apiCall } from '../utility/api';
|
|
||||||
import { useAppFolders } from '../utility/metadataLoaders';
|
|
||||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
|
||||||
|
|
||||||
let filter = '';
|
|
||||||
|
|
||||||
$: folders = useAppFolders();
|
|
||||||
|
|
||||||
const handleRefreshFolders = () => {
|
|
||||||
apiCall('apps/refresh-folders');
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<SearchBoxWrapper>
|
|
||||||
<SearchInput placeholder="Search applications" bind:value={filter} />
|
|
||||||
<CloseSearchButton bind:filter />
|
|
||||||
<InlineButton on:click={() => runCommand('new.application')} title="Create new application">
|
|
||||||
<FontIcon icon="icon plus-thick" />
|
|
||||||
</InlineButton>
|
|
||||||
<InlineButton on:click={handleRefreshFolders} title="Refresh application list">
|
|
||||||
<FontIcon icon="icon refresh" />
|
|
||||||
</InlineButton>
|
|
||||||
</SearchBoxWrapper>
|
|
||||||
<WidgetsInnerContainer>
|
|
||||||
<AppObjectList list={_.sortBy($folders, 'name')} module={appFolderAppObject} {filter} />
|
|
||||||
</WidgetsInnerContainer>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import AppFilesList from './AppFilesList.svelte';
|
|
||||||
|
|
||||||
import WidgetColumnBar from './WidgetColumnBar.svelte';
|
|
||||||
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
|
|
||||||
|
|
||||||
import { useFavorites } from '../utility/metadataLoaders';
|
|
||||||
import AppFolderList from './AppFolderList.svelte';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<WidgetColumnBar>
|
|
||||||
<WidgetColumnBarItem title="Applications" name="apps" height="30%" storageName="appsWidget">
|
|
||||||
<AppFolderList />
|
|
||||||
</WidgetColumnBarItem>
|
|
||||||
|
|
||||||
<WidgetColumnBarItem title="Application files" name="files" storageName="appFilesWidget">
|
|
||||||
<AppFilesList />
|
|
||||||
</WidgetColumnBarItem>
|
|
||||||
</WidgetColumnBar>
|
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||||
import { isProApp } from '../utility/proTools';
|
import { isProApp } from '../utility/proTools';
|
||||||
import InlineUploadButton from '../buttons/InlineUploadButton.svelte';
|
import InlineUploadButton from '../buttons/InlineUploadButton.svelte';
|
||||||
|
import { DATA_FOLDER_NAMES } from 'dbgate-tools';
|
||||||
|
|
||||||
let filter = '';
|
let filter = '';
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
const dbCompareJobFiles = useFiles({ folder: 'dbcompare' });
|
const dbCompareJobFiles = useFiles({ folder: 'dbcompare' });
|
||||||
const perspectiveFiles = useFiles({ folder: 'perspectives' });
|
const perspectiveFiles = useFiles({ folder: 'perspectives' });
|
||||||
const modelTransformFiles = useFiles({ folder: 'modtrans' });
|
const modelTransformFiles = useFiles({ folder: 'modtrans' });
|
||||||
|
const appFiles = useFiles({ folder: 'apps' });
|
||||||
|
|
||||||
$: files = [
|
$: files = [
|
||||||
...($sqlFiles || []),
|
...($sqlFiles || []),
|
||||||
@@ -41,32 +43,18 @@
|
|||||||
...($modelTransformFiles || []),
|
...($modelTransformFiles || []),
|
||||||
...((isProApp() && $dataDeployJobFiles) || []),
|
...((isProApp() && $dataDeployJobFiles) || []),
|
||||||
...((isProApp() && $dbCompareJobFiles) || []),
|
...((isProApp() && $dbCompareJobFiles) || []),
|
||||||
|
...((isProApp() && $appFiles) || []),
|
||||||
];
|
];
|
||||||
|
|
||||||
function handleRefreshFiles() {
|
function handleRefreshFiles() {
|
||||||
apiCall('files/refresh', {
|
apiCall('files/refresh', {
|
||||||
folders: [
|
folders: DATA_FOLDER_NAMES.map(folder => folder.name),
|
||||||
'sql',
|
|
||||||
'shell',
|
|
||||||
'markdown',
|
|
||||||
'charts',
|
|
||||||
'query',
|
|
||||||
'sqlite',
|
|
||||||
'diagrams',
|
|
||||||
'perspectives',
|
|
||||||
'impexp',
|
|
||||||
'modtrans',
|
|
||||||
'datadeploy',
|
|
||||||
'dbcompare',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function dataFolderTitle(folder) {
|
function dataFolderTitle(folder) {
|
||||||
if (folder == 'modtrans') return 'Model transforms';
|
const foundFolder = DATA_FOLDER_NAMES.find(f => f.name === folder);
|
||||||
if (folder == 'datadeploy') return 'Data deploy jobs';
|
return foundFolder ? foundFolder.label : _.startCase(folder);
|
||||||
if (folder == 'dbcompare') return 'Database compare jobs';
|
|
||||||
return _.startCase(folder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUploadedFile(filePath, fileName) {
|
async function handleUploadedFile(filePath, fileName) {
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
import SearchInput from '../elements/SearchInput.svelte';
|
import SearchInput from '../elements/SearchInput.svelte';
|
||||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||||
import {
|
import {
|
||||||
|
useAllApps,
|
||||||
useConnectionInfo,
|
useConnectionInfo,
|
||||||
useDatabaseInfo,
|
useDatabaseInfo,
|
||||||
useDatabaseStatus,
|
useDatabaseStatus,
|
||||||
useSchemaList,
|
useSchemaList,
|
||||||
useUsedApps,
|
|
||||||
} from '../utility/metadataLoaders';
|
} from '../utility/metadataLoaders';
|
||||||
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
||||||
import AppObjectList from '../appobj/AppObjectList.svelte';
|
import AppObjectList from '../appobj/AppObjectList.svelte';
|
||||||
@@ -73,9 +73,8 @@
|
|||||||
$: connection = useConnectionInfo({ conid });
|
$: connection = useConnectionInfo({ conid });
|
||||||
$: driver = findEngineDriver($connection, $extensions);
|
$: driver = findEngineDriver($connection, $extensions);
|
||||||
|
|
||||||
$: apps = useUsedApps();
|
$: apps = useAllApps();
|
||||||
|
$: appsForDb = filterAppsForDatabase($connection, database, $apps || [], $objects);
|
||||||
$: dbApps = filterAppsForDatabase($currentDatabase?.connection, $currentDatabase?.name, $apps || []);
|
|
||||||
|
|
||||||
// $: console.log('OBJECTS', $objects);
|
// $: console.log('OBJECTS', $objects);
|
||||||
|
|
||||||
@@ -87,13 +86,14 @@
|
|||||||
['schemaName', 'pureName']
|
['schemaName', 'pureName']
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
...dbApps.map(app =>
|
...appsForDb.map(app =>
|
||||||
app.queries.map(query => ({
|
Object.values(app.files || {})
|
||||||
objectTypeField: 'queries',
|
.filter(x => x.type == 'query')
|
||||||
pureName: query.name,
|
.map(query => ({
|
||||||
schemaName: app.name,
|
objectTypeField: 'queries',
|
||||||
sql: query.sql,
|
pureName: query.label,
|
||||||
}))
|
sql: query.sql,
|
||||||
|
}))
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
>
|
>
|
||||||
<AppObjectList
|
<AppObjectList
|
||||||
list={objectList
|
list={objectList
|
||||||
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
|
.filter(x => x.schemaName == null || ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
|
||||||
.map(x => ({ ...x, conid, database }))}
|
.map(x => ({ ...x, conid, database }))}
|
||||||
module={databaseObjectAppObject}
|
module={databaseObjectAppObject}
|
||||||
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
|
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import PluginsWidget from './PluginsWidget.svelte';
|
import PluginsWidget from './PluginsWidget.svelte';
|
||||||
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 AdminMenuWidget from './AdminMenuWidget.svelte';
|
import AdminMenuWidget from './AdminMenuWidget.svelte';
|
||||||
import AdminPremiumPromoWidget from './AdminPremiumPromoWidget.svelte';
|
import AdminPremiumPromoWidget from './AdminPremiumPromoWidget.svelte';
|
||||||
import PublicCloudWidget from './PublicCloudWidget.svelte';
|
import PublicCloudWidget from './PublicCloudWidget.svelte';
|
||||||
@@ -14,8 +13,9 @@
|
|||||||
import hasPermission from '../utility/hasPermission';
|
import hasPermission from '../utility/hasPermission';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DatabaseWidget hidden={$visibleSelectedWidget != 'database'} />
|
{#if hasPermission('widgets/database')}
|
||||||
|
<DatabaseWidget hidden={$visibleSelectedWidget != 'database'} />
|
||||||
|
{/if}
|
||||||
{#if $visibleSelectedWidget == 'file' && hasPermission('widgets/file')}
|
{#if $visibleSelectedWidget == 'file' && hasPermission('widgets/file')}
|
||||||
<FilesWidget />
|
<FilesWidget />
|
||||||
{/if}
|
{/if}
|
||||||
@@ -31,9 +31,6 @@
|
|||||||
{#if $visibleSelectedWidget == 'cell-data' && hasPermission('widgets/cell-data')}
|
{#if $visibleSelectedWidget == 'cell-data' && hasPermission('widgets/cell-data')}
|
||||||
<CellDataWidget />
|
<CellDataWidget />
|
||||||
{/if}
|
{/if}
|
||||||
{#if $visibleSelectedWidget == 'app' && hasPermission('widgets/app')}
|
|
||||||
<AppWidget />
|
|
||||||
{/if}
|
|
||||||
{#if $visibleSelectedWidget == 'admin' && hasPermission('widgets/admin')}
|
{#if $visibleSelectedWidget == 'admin' && hasPermission('widgets/admin')}
|
||||||
<AdminMenuWidget />
|
<AdminMenuWidget />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -110,13 +110,6 @@
|
|||||||
hasPermission('settings/change') && { command: 'settings.show' },
|
hasPermission('settings/change') && { command: 'settings.show' },
|
||||||
{ command: 'theme.changeTheme' },
|
{ command: 'theme.changeTheme' },
|
||||||
hasPermission('settings/change') && { command: 'settings.commands' },
|
hasPermission('settings/change') && { command: 'settings.commands' },
|
||||||
hasPermission('widgets/app') && {
|
|
||||||
text: 'View applications',
|
|
||||||
onClick: () => {
|
|
||||||
$selectedWidget = 'app';
|
|
||||||
$visibleWidgetSideBar = true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hasPermission('widgets/plugins') && {
|
hasPermission('widgets/plugins') && {
|
||||||
text: 'Manage plugins',
|
text: 'Manage plugins',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user